如何解决线程不安全问题以及java中两种加锁
Posted 讵有君@
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何解决线程不安全问题以及java中两种加锁相关的知识,希望对你有一定的参考价值。
要解决线程不安全问题,首先要了解导致线程不安全的因素:
- CPU是抢占式执行的(万恶之源)
- 多个线程同时修改同一个变量(即共享变量)
- 可见性
- 原子性
- 指令重排序
1、2两种因素,是我们无法改变的,所以我们只能解决剩下的因素。
解决方案
-
用volatile修饰
解决可见性:
每个线程都有自己的工作内存,假设有两个线程,对一个count值进行修改,按一般情况是线程1对count修改为1再放入主内存,线程2再对count修改为-1,再存入主内存,但此时线程1的工作内存中count还是为1(即线程2的修改对线程1是不可见的)。但是对count进行volatile修饰,所有的线程再修改完成之后就会强制清除掉自己工作内存中的count,之后所有线程在自己工作内存中就找不到count了,就都会去找主内存的值,从而解决了内存不可见问题。解决指令重排序问题:禁止了指令重排序
-
java中两种加锁(synchronized 和 Lock)
java中加锁
首先要先了解操作锁的流程:
①尝试获取锁;
②使用锁;
③释放锁。
我们主要关注获取锁和释放锁。
1. synchronized(jvm层的解决方案)
synchronized的底层是使用操作系统的mutex lock实现的。
当线程释放锁时,JMM(java内存模型)会把该线程对应的工作内存中的 共享变量刷新到主内存中;
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
synchronized用的锁是存在Java对象头里的
synchronized的3种使用场景:
①可以修饰代码块(可给任意对象加锁)
②可以修饰静态方法(对当前的类进行加锁)
③可以修饰普通方法(对当前类实例进行加锁)
2. Lock手动锁
步骤:
①创建Lock实例
Lock lock = new ReetrantLock();
②加锁
lock.lock();
③释放锁
lock.unlock();
注意事项:
②一定要放在try外面,③一定要放在finally里面。
在java语言中,所有的锁都默认实现方式为非公平锁。
非公平锁调度:当一个线程释放锁之后,刚好有另一个线程执行到获取锁的代码,所以就直接获取锁。
公平锁调度:一个线程释放锁,然后主动唤醒需要得到锁的队列来得到锁,会有性能消耗。
所以非公平锁的性能更高。
synchronized是非公平锁,ReetrantLock也是非公平锁,但是也可以显式的声明为公平锁:
Lock lock = new ReetrantLock(true);
sychronized 和 Lock 的区别:
- 关键字不同;
- 前者自动加锁和释放锁,后者手动加锁和释放锁;
- Lock是java层面来实现,而sychronized是jvm层面来实现;
- 适用范围不同:Lock只能修饰代码块,而sychronized可以修饰前面提到的三个场景;
- sychronized锁的模式只有公平锁模式。
以上是关于如何解决线程不安全问题以及java中两种加锁的主要内容,如果未能解决你的问题,请参考以下文章