java多线程并发编程复习笔记(2)Java线程之间的共享和协作Volatile、Synchronized、ThreadLocal

226 阅读5分钟

一、volatile,最轻量的同步机制

1.Volatile介绍

volatile:保证了不同线程对这个变量进行操作的可见性。即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

不加volatile时,某个线程修改了一个变量,其他线程不一定知道这个变量被修改了。(一个线程修改了成员变量的值,不确定什么时候主存里的成员变量的数据也会被修改,自然别的线程也不知道什么时候被修改了)。

为了更好的理解,这里穿插个JMM(java内存模型)的内容:每个线程有自己的工作内存,每个线程的操作只能在自己的工作内存中进行,它不能改别的线程里的工作内存的内容,也不能改变主内存里数据的内容。

2.Volatile的不足

他只能保证数据的可见性,但不能保证操作的原子性,及线程安全性。

3.概念补充

  • 可见性:一个变量在一个线程里进行了修改,会直接修改主存里的数据,并使其它线程的备份数据标为不可用,其它线程在使用修改了的数据时会重新去主存里取数据。
  • 原子性:一段代码(一系列的指令),要么不执行,要么一次性执行完,中间不会穿插其它指令的执行。
  • 线程安全:当一个线程在写或者修改一个数据的时候,其它线程不能修改这个数据。

二、Synchronized内置锁

1.介绍

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。

2.对象锁和类锁

其实对象锁和类锁都是锁的对象,只不过对象锁锁的是对象实例,类锁锁的是.class对象。

  1. 对象锁:锁的时具体的对象。举例:非静态方法加上synchronized关键字。代码块里synchronized(),括号里放入具体的对象。
  2. 类锁:锁的是.class类对象。举例:静态方法加上synchronized关键字。代码块里synchronized(),括号里放入具体的****.class

概念:

1.一个类对象的实例可以有很多个,但一个.class对应的类对象只能有一个。

2.类对象只能由JVM创建,是在.class对象被加载的时候创建的,并且只会创建一次。

3.原理

  1. synchronized说白了就是个对象锁,锁的状态就保存在锁的这个对象的对象头里。

image.png

  1. 所以synchrozied有3种锁的状态:偏向锁,轻量级锁、重量级锁

  2. 偏向锁:当一个线程访问被锁方法时,会修改对象头为偏向锁,后续访问会根据线程的ID来判断是否是之前的线程,如果是,则直接进入。否则的话,会stw(stop the world)等待上一个线程执行完成后,修改对象头为轻量级锁。

  3. 轻量级锁:采用的是循环锁的逻辑+CAS实现,当锁的竞争不激烈的时候,采用CAS的方式可以减少线程上下文的切换,提高性能。

  4. 自适应自旋锁(Synchrozined的优化):当自旋的时间超过了上下文切换的时候就会升级成重量级锁。

  5. 重量级锁:没有获得锁的线程进入阻塞队列,线程处于阻塞状态。

4.容易出错的加锁用例

image.png

根据反编译的字节码可以看出i++的操作是Integer.ValueOf()的操作,而这个操作最后执行的是:

image.png 所以一但i++执行完了后,每个线程进入同步代码块的时候i对象都不是同一个对象。

三、ThreadLocal

1.介绍

ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。

2.原理

image.png

  1. 根据当前线程获取当前线程对象里的ThreadlLocalMap对象。

image.png 2. 再通过以ThdreadLocal为key,ThreadLocal保存的值为Value的ThreadlLocalMap。获取,存入数据。

3.使用的问题和缺陷

  1. 要么在初始化的ThreadLocal的时候重写initialValue的方法,创新初始对象。

  2. 在线程掉用ThreadLocl的set方法的时候,需要用线程里创建的对象,不要用静态对象和线程这个类创建以外的对象。因为对象传的是引用,在线程的工作内存里使用的也是堆里的对象。所以一个线程修改了,另外一个线程里的对象也会被修改。

  3. ThreadLocal使用不当会发生内存泄漏:

    1)当ThreadLocal的变量赋值为null的时候,当GC的时候就会把创建的ThreadLocal对象回收,此时ThreadLocal对象保存在ThreadlLocalMap里的Value就获取不到了。导致内存泄漏。

    2)所以当要把ThreadLocal的变量赋值为null的时候,应该掉用ThreadLocal.remove()方法,把这个threadLocal移除。

    3)ThreadLocalMap在线程执行完成后就会被回收。