线程安全问题的发生:
java的线程内存模型中定义了每个线程都有一份自己的共享变量副本(本地内存),里面存放自己私有的数据,其他线程不能直接访问,而一些共享变量则存在主内存中,供所有线程访问。
上图中,如果线程A和线程B要进行通信,就要经过主内存,比如线程B要获取线程A修改后的共享变量的值,要经过下面两步:
(1)、线程A修改自己的共享变量副本,并刷新到了主内存中。
(2)、线程B读取主内存中被A更新过的共享变量的值,同步到自己的共享变量副本中。
总结:在java内存模型中,共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
JAVA多线程的三个特性: 原子性、可见性、有序性
Volatile关键字作用:
(1)volatile关键字的作用就是保证了可见性和有序性(不保证原子性),如果一个共享变量被volatile关键字修饰,那么如果一个线程修改了这个共享变量后,其他线程是立马可知的。线程A修改了共享变量副本,此时如果该变量被volatile修饰,那么本次修改结果会立即刷新到主存中.
(2)volatile禁止指令重排序优化,保证了有序性。
(3)volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令
synchronized关键字的作用:
(1)volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。
(2)volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。
(3)volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。
(4)在性能方面synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。
指令重排:
指令重排是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。但是重排序可以保证最终执行的结果是与程序顺序执行的结果一致,并且只会对不存在数据依赖性的指令进行重排序,这个重排序在单线程下对最终执行结果是没有影响的,但是在多线程下就会存在问题。
如何判断锁现象中锁的是谁?知道锁到底锁的是谁?
(1)synchronized修饰的方法---锁的是调用这个方法的对象,普通方法是不受锁的影响的,举例:
(2) static是静态修饰变量,当类一加载时就有了,而phone是对象,只有当对象创建时才会有
static对应的是类模板,全局唯一,这个模板创建的对象可以有多个,但是这个模板只有一个
所以,方法加了static之后,再被synchronized修饰,锁的就是模板,而不是对象了,在这个模板上的多个对象也会在锁内。举例:
--> 当一个对象的不同synchronized方法,拿到的是同一把锁
--> 当加了static 的synchronized方法,是多个对象之间的一把锁
两把锁不一样,相互之间没有影响
1、一个对象,一个synchronized方法,一个对象,普通方法无关锁
2、多个对象,一个synchronized方法,多把锁
3、多个对象,多个synchronized方法,多把锁
4、一个对象,多个synchronized方法是一个锁,普通方法无关
5、多个对象,多个static synchronized方法,锁了模板,成了一个锁
6、单个对象,一个synchronized方法,一个static synchronized方法,不同的两把锁,一个模板锁、一个对象锁,相互不影响
7、两个对象,一个synchronized方法,一个static synchronized方法,不同的三把锁,一个模板锁,两个对象锁