一、volatile,最轻量的同步机制
1.Volatile介绍
volatile:保证了不同线程对这个变量进行操作的可见性。即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
不加volatile时,某个线程修改了一个变量,其他线程不一定知道这个变量被修改了。(一个线程修改了成员变量的值,不确定什么时候主存里的成员变量的数据也会被修改,自然别的线程也不知道什么时候被修改了)。
为了更好的理解,这里穿插个JMM(java内存模型)的内容:每个线程有自己的工作内存,每个线程的操作只能在自己的工作内存中进行,它不能改别的线程里的工作内存的内容,也不能改变主内存里数据的内容。
2.Volatile的不足
他只能保证数据的可见性,但不能保证操作的原子性,及线程安全性。
3.概念补充
- 可见性:一个变量在一个线程里进行了修改,会直接修改主存里的数据,并使其它线程的备份数据标为不可用,其它线程在使用修改了的数据时会重新去主存里取数据。
- 原子性:一段代码(一系列的指令),要么不执行,要么一次性执行完,中间不会穿插其它指令的执行。
- 线程安全:当一个线程在写或者修改一个数据的时候,其它线程不能修改这个数据。
二、Synchronized内置锁
1.介绍
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。
2.对象锁和类锁
其实对象锁和类锁都是锁的对象,只不过对象锁锁的是对象实例,类锁锁的是.class对象。
- 对象锁:锁的时具体的对象。举例:非静态方法加上synchronized关键字。代码块里synchronized(),括号里放入具体的对象。
- 类锁:锁的是.class类对象。举例:静态方法加上synchronized关键字。代码块里synchronized(),括号里放入具体的****.class
概念:
1.一个类对象的实例可以有很多个,但一个.class对应的类对象只能有一个。
2.类对象只能由JVM创建,是在.class对象被加载的时候创建的,并且只会创建一次。
3.原理
- synchronized说白了就是个对象锁,锁的状态就保存在锁的这个对象的对象头里。
-
所以synchrozied有3种锁的状态:偏向锁,轻量级锁、重量级锁
-
偏向锁:当一个线程访问被锁方法时,会修改对象头为偏向锁,后续访问会根据线程的ID来判断是否是之前的线程,如果是,则直接进入。否则的话,会stw(stop the world)等待上一个线程执行完成后,修改对象头为轻量级锁。
-
轻量级锁:采用的是循环锁的逻辑+CAS实现,当锁的竞争不激烈的时候,采用CAS的方式可以减少线程上下文的切换,提高性能。
-
自适应自旋锁(Synchrozined的优化):当自旋的时间超过了上下文切换的时候就会升级成重量级锁。
-
重量级锁:没有获得锁的线程进入阻塞队列,线程处于阻塞状态。
4.容易出错的加锁用例
根据反编译的字节码可以看出i++的操作是Integer.ValueOf()的操作,而这个操作最后执行的是:
所以一但i++执行完了后,每个线程进入同步代码块的时候i对象都不是同一个对象。
三、ThreadLocal
1.介绍
ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
2.原理
- 根据当前线程获取当前线程对象里的ThreadlLocalMap对象。
2. 再通过以ThdreadLocal为key,ThreadLocal保存的值为Value的ThreadlLocalMap。获取,存入数据。
3.使用的问题和缺陷
-
要么在初始化的ThreadLocal的时候重写initialValue的方法,创新初始对象。
-
在线程掉用ThreadLocl的set方法的时候,需要用线程里创建的对象,不要用静态对象和线程这个类创建以外的对象。因为对象传的是引用,在线程的工作内存里使用的也是堆里的对象。所以一个线程修改了,另外一个线程里的对象也会被修改。
-
ThreadLocal使用不当会发生内存泄漏:
1)当ThreadLocal的变量赋值为null的时候,当GC的时候就会把创建的ThreadLocal对象回收,此时ThreadLocal对象保存在ThreadlLocalMap里的Value就获取不到了。导致内存泄漏。
2)所以当要把ThreadLocal的变量赋值为null的时候,应该掉用ThreadLocal.remove()方法,把这个threadLocal移除。
3)ThreadLocalMap在线程执行完成后就会被回收。