一 、什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1:多线程并发
条件2:有共享数据
条件3:共享数据有修改的行为
满足以上三个条件之后,就会存在线程安全问题。
举个与女朋友取钱的例子:
(我和女朋友同时去柜台取钱,在我取完,余额还没变得一瞬间女朋友也在取,取完后才更新;如果银行线程存在安全问题的话,那银行就亏了一万块。)
线程安全一般是用来保护Java中的三种变量的;
Java中的三种变量:
实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
注意:局部变量永远不会存在线程安全问题,因为局部变量不共享(一个线程一个栈)
局部变量在栈中,所以局部变量永远不会共享。
实例变量在堆中,堆只有一个;静态变量在方法区中,方法区也只有一个。所以方法去和堆都是多线程共享的,可能存在安全问题。(😶🌫️重点😶🌫️)
(也就是成员变量才可能存在线程安全问题)
二 、怎么解决线程安全问题呢?
线程排队执行(不能并发),
用排队执行解决线程安全问题,
这种机制被称为:线程同步机制。专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
怎么解决线程安全问题呢?
答:“使用线程同步机制”。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,怎么谈效率呢?
(按上述例子说就是我取完后,余额变化后,女朋友才能去取钱,这时候银行(线程)就安全了)
线程同步
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型称为异步编程模型。(其实就是多线程并发(效率较高))
“异步就是并发”
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,俩个线程之间发生了等待关系,这就是同步编程模型。(效率较底,线程排队执行)
“同步就是排队”
不用join()方法去解决线程安全问题
注意:在没学后面之前,很多人第一直觉就是利用join()方法去解决这个安全问题,但首先这是低效的(因为调用这个方法是等待这个线程执行结束后,下面其他线程才进入并发),而且也是不好操作的,一般是在主线程里调用的该方法,而且还会有异常。所以不用这个方法进行解决线程安全问题。
利用synchronized关键字
synchronized三种写法:
形式1:线程同步机制的语法是:
synchronized(){
//线程同步代码块
}
synchronized后面小括号中传的数据是相当重要的。这个数据必须是多线程共享的数据,才能达到多线程排队。小括号里面写什么?(只要是共享对象就行)那要看想要哪些线程同步,假设t1,t2,t3,t4,t5,有5个线程只希望t1,t2,t3排队,t4,t5不排队,怎么办?
你一定要在小括号里写一个t1,t2,t3共享的对象。而这个对象对于t4,t5来说不是共享的。形式2:在实例方法上运用synchronized。这样用的话就相当于synchronized小括号里的对象是this,所以这种方式不灵活。
还有一个缺点:synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步范围,程序执行效率会降低,所以这种方式不常用。(但如果共享对象就是this还是建议用这种的,因为写法比较简单)
(形式1和形式2保护实例变量)
形式3:在静态方法上用synchronized,表示找类锁,类锁永远只有一把,就算创建一百个对象,类锁也只有一把。
(一个对象一把锁,一个类就一把锁)synchronized实际就是一种锁机制。
面试实例理解synchronized关键字
题1:
题2:
题3:
题4:
开发中应该怎么解决线程安全问题呢?
1、 尽量使用局部变量代替”实例变量和静态变量“。
2、如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应着一百个对象,对象不共享,就不存在数据安全问题了。)
3、如果不能使用局部变量,也不能创建更多个对象,那就只能选择synchronized了。线程同步机制。