目录
一、(重点)关于多线程并发环境下,数据的安全问题
1.1 什么时候数据在多线程并发的情况下存在安全问题?
1.2 那么怎么解决这个问题呢?
1.3. 线程同步,涉及以下两个专业术语:
1.4 三种同步机制:
1.4.1 同步代码块
1.4.2 同步方法
1.4.3 还可以在静态方法上使用synchronized(锁当前类)
1.4.3 Lock 锁
1.4 关于线程安全(银行账户)案例:
1.5 线程私有数据和线程共享数据对多线程并行有什么影响吗?
二、内存结构与线程安全
线程安全问题的出现主要是因为多线程并发访问共享资源时可能导致的竞态条件(Race Condition)和并发访问冲突。以下是一些常见的原因:
为了解决线程安全问题,开发者需要采用适当的同步机制(如锁、信号量、条件变量等),以确保多线程之间的协调和互斥,防止竞态条件和并发问题的发生。此外,选择合适的数据结构和算法,以及遵循最佳的并发编程实践,也是确保线程安全的关键。
有三个条件:
- 条件一:多线程并发
- 条件二:有共享数据
- 条件三:共享数据有修改的行为
- 采用“线程同步机制”
- 即线程排队执行,不能并发。也就是说把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- 异步编程模型:
- 线程t1和t2,各自执行各自的,谁也不需要等谁,也就是:多线程并发(效率较高)
- 同步编程模型:
- 线程t1和t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说线程t2执行的时候,必须等待t1线程结束,两个线程之间发生了等待关系。即线程排队执行。(效率较低)
线程同步机制的语法:synchronized (){
线程同步代码块 }
\* synchronized ()这个括号里面传递的数据是非常关键的。并且不能为null值
\* 这个数据必须是多线程共享的数据。才能达到多线程排队。
那么()中写的就是你想让哪些线程同步。
假设有t1,t2,t3,t4,这四个线程,你只希望t1,t2排队,
那么你一定要在()中写一个t1,t2共享的对象。而这个对象对t3,t4来说不是共享的。。。。
下面用synchronized保证线程安全问题
`synchronized `关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
语法:
synchronized(同步锁){
//同步的代码
}
这里的同步锁:
对象的同步锁只是一个概念,可以想象为在任意对象上标记了一个锁,具体的就是上面讲述的。
使用 synchronized 修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){
//可能产生安全问题的代码
}
那么同步方法中的同步锁是谁呢?
对于“实例方法” ,同步锁就是 this 。
对于 “static 方法” ,我们使用当前方法所在类的字节码对象(类名.class)
给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的 锁。这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定 实例,被类的所有实例共享。
synchronized static void method() {
//业务代码
}
`java.util.concurrent.locks.Lock `机制提供了比 synchronized 代码块和 synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能 Lock 都有,除此之外更强大,更体现面向对象。在 JDK 5 引入了 `ReentrantLock `, `ReentrantLock` 重入锁,是实现` Lock 接口`的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。支持公平锁和非公平锁两种方式。
- `public void lock() :`加同步锁。
- `public void unlock()` :释放同步锁
代码演示:
public class Ticket implements Runnable {
private int ticket = 100;
private Lock lock = new ReentrantLock();
/**
* 执行卖票操作
*/
@Override
public void run() {
while (true) {
// 加同步锁
lock.lock();
//产生安全问题的方法
sellTicket();
// 释放同步锁
lock.unlock();
}
}
private void sellTicket(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖第" + ticket-- + "张票");
}
}
}
//关于线程安全(银行账户):
/*不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题*/
public class test03 {
public static void main(String[] args) {
//创建账户对象,只创建一个
Account2 account2 = new Account2("act01",10000);
//创建两个线程对同一个账户取款
Thread t1 = new AccountThread(account2);
Thread t2 = new AccountThread(account2);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class AccountThread extends Thread{
//两个线程必须共享同一个账户对象
private Account2 act = null;//实例变量
//通过构造方法把账户对象传递过来
public AccountThread (Account2 act){
this.act = act;
}
@Override
public void run() {
//run方法的执行表示取款操作
double money = 5000;
//取款
/*synchronized (act){ //这种方法也可以,只不过扩大了同步的范围,效率更低
act.withdraw(money);
}*/
act.withdraw(money);
System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额"+act.getBanlance());
}
}
class Account2{
//账号
private String actno;
//余额
private double banlance;
public Account2(){ }
public Account2(String actno,double banlance){
this.actno = actno;
this.banlance = banlance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBanlance() {
return banlance;
}
public void setBanlance(double banlance) {
this.banlance = banlance;
}
//取款的方法
//synchronized也可以出现在实例方法中:
//但是出现在这里,一定锁的是this,不能是其他的对象了,所以这种方式不灵活,不常用
//public synchronized void withdraw(double money){
public void withdraw(double money){
//t1和t2 并发这个方法,(t1和t2是两个栈,两个栈操作堆中的同一个对象)
//解决线程安全问题:
/*以下的这几行代码必须是线程排队的,不能并发。
* 一个线程把这里的全部代码执行完毕之后,另一个线程才能执行。*/
//synchronized ("adsfs"){ //---->字符串也是可以的,只不过t1,t2,t3,t4都可以共享了
synchronized (this){ //--->只有t1,t2才可以共享了
//取款之前的余额
double befor = this.getBanlance();
//取款之后的余额
double after = befor-money;
//在这里模拟一下网络延迟,1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
this.setBanlance(after);
}
}
}
##### 执行结果:
`t2对act01取款成功,余额5000.0`
`t1对act01取款成功,余额0.0`
所有线程的共享数据都在堆中
|
3.2 从对象状态看线程安全
如果你觉得博主的文章的不错或者对你有帮助,可以点一个免费的关注支持一下博主,你的鼓励将是我创作的最大动力!!