每个工作线程都需要从主存中拷贝一份变量到自己的工作内存中,当一个变量被lock的时候,会清除其在工作内存中的数据,等使用的时候重新从主存中读取。
当变量的锁被一个线程占有之后,其他线程就不能获取到他的锁,所以无法从主存中获取到他的数据,只能等待锁的释放,从而确保了数据的安全性。
而被标记了volatile的变量,当发生变更的时候会通知到其他使用它的线程,使其全局可见,但是不能保证其数据的安全性,依旧存在竞态条件问题。
JVM会把64位的long和double拆分成两个32位进行操作,因此存在安全性问题。
如何保证线程安全
线程封闭
1、Ad-hoc线程封闭
例如Volatile就是一个特殊的情况,当只有一个线程写入,其他线程只读的情况下就是线程安全的
2、栈封闭
方法内的局部变量都是线程安全的
3、ThreadLocal
ThreadLocal类对于每个线程会有一份独立的副本,每个线程之间的数据都是相互独立不受影响的,例如数据库的连接池
不变性
1、final
final对象是不可变的,所以正确构造的final对象一定是线程安全的
对象创建以后其状态就不能修改
对象的所有域都是final类型
对象是正确创建的
除非需要更高的可见性,否则应该将所有的域都设置为private
2、使用线程安全的容器
k-v:Hashtable、synchronizedMap、ConcurrentMap、ConcurrentHashMap
value:Vector、CopyOnWriteArrayList、CopyOnWriteArraySet
队列:BlockingQueue、ConcurrentLinkedQueue
3、锁
synchronized
volatile
-
ConcurrentHashMap
具有弱一致性,可以在迭代的时候修改元素,并且不会抛出MidificationException,但是size可能不准确 -
CopyOnWriteArrayList
当写入的时候会先生成一个副本
同步工具类
闭锁
CountDownLatch--指定启动时的状态,await方法会一直阻塞,直到countDown到0为止会往下执行,适合做并发测试
栅栏
CyclicBarrier--定义一个值以及需要执行的任务,当等待的线程数量与其一致的时候就开始执行任务,否则就阻塞,可以重复使用
FutureTask
FutureTask.get()
信号量
Semaphore--用于控制同一时间某个特定资源的操作数量,通过acquire个release这两个方法进行限制
Executors
newFixedThreadPool
newCachedThreadPool
newSingleThreadPool
newScheduledThreadPool
[https://www.processon.com/diagraming/5f490aa85653bb0c71dd9c8a](https://www.processon.com/diagraming/5f490aa85653bb0c71dd9c8a)
[https://www.processon.com/diagraming/5f4bae5ae401fd14b225a8c8](https://www.processon.com/diagraming/5f4bae5ae401fd14b225a8c8)
线程的关闭
Interrupt
调用未知的代码要使用try catch,避免因异常而导致线程挂掉
FutureTask.get()可以获取到异常,可以根据异常来进行处理
shutdown--等待任务处理完成之后安全退出
shutdownNow--立刻结束当前任务,并返回未执行任务列表
处理非正常异常的时候可以通过setUncaughtExceptionHandler来进行处理
JVM钩子
通过Runtime.getRuntime().addShutdownHook添加钩子
守护线程与普通线程
普通线程会等待,守护线程直接结束
网友评论