如何实现线程安全?

谁说我不能喝 提交于 2020-01-26 15:08:00

什么是线程安全?下面是我摘自java并发编程书里给的解释:

当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

这个解释听的是不是云里雾里?我们说的通俗一点:

线程安全就是多线程并发访问的时候,线程之间所共享的资源不会被污染或被破坏。

我们知道线程共享的内存区域主要就是java堆,而java堆主要存的就是对象和数组,所有的全局变量都存储在堆中。也就是说我们想保证线程安全,就需要保证全局变量和堆内的对象不会受其他线程影响。

由此,我们可以从数据共享和不共享两个方面来讨论实现线程安全的方式。

避免共享

通过避免共享,让所有资源都编程线程私有,达到线程安全。主要实现方法有三种:

  1. 不可变
  2. 可重入代码
  3. 线程封闭

不可变

使用final类型
通过使用final类型修饰,发布不可变对象。
String属于不可变对象,可直接使用。

使用volatile类型
通过使用Volatile类型修饰,发布不可变对象。

volatile类型修饰的变量只适用于读多写少的情况,只能确保其可见性。如果要保证线程安全,则需要保证只有单个线程去修改。

可重入代码

若一个程序或子程序可以安全的被并行执行,则称其为可重入(reentrant),即允许多个进程同时访问。

可重入代码:所有变量都是局部变量或者是不可变的全局变量。

线程封闭

通过使用线程本地存储,即使用ThreadLocal类存储对象。

ThreadLocal提供类get()和set()等方法,为每个使用该变量的线程都保存一份独立的副本。

Thread类中有一个成员变量ThreadLocal.ThreadLocalMap,ThreadLocalMap则定义在ThreadLocal类中的静态内部类,它是一个Map,他的key是线程对象,value是线程的变量副本。

必须共享

保证数据共享安全,即保证数据同步。同步的方式主要分两种:互斥同步和非阻塞同步。

互斥同步

为了保证多线程在同一时间访问共享数据的安全性,我们可以通过加互斥锁使共享数据每次只被一个线程访问,这样就能保证线程安全,但是同时线程会被阻塞。
互斥同步的主要实现方式是加锁:synchronized 和 ReentrantLock。

synchronized关键字:java内置锁,可重入。同步代码块的锁就是方法调用所在的对象
,静态的synchronized方法以Class对象作为锁。进入占有锁,退出释放锁(无论是正常还是异常退出)。

ReentrantLock:实现lock接口,默认的是非公平锁 false,必须手动释放锁
ReentrantLock相比synchronized优势:1. 可实现公平锁 (构造参数传true) 2. 可响应中断 3. 获取锁限时等待 4. 结合Condition实现等待通知机制

非阻塞同步

java提供了大量的基于非阻塞同步CAS原子操作的类(java.util.concurrent 里面Atomic开头的类)。

CAS 又称无锁操作,一种乐观锁策略,原理就是多线程环境下各线程访问共享变量不会加锁阻塞排队,线程不会被挂起。通俗来讲就是一直循环对比,如果有访问冲突则重试,直到没有冲突为止。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!