解决线程不安全问题的三种方法

解决线程不安全问题的三种方法
一.volatile:轻量级解决“线程安全”的方案
1.作用:

  1. 禁止指令重排序
  2. 解决线程可见性的问题,实现原理是当操作完变量之后,强制删除掉线程工作内存中的此变量。
    注意事项:
    volatile不能解决原子性问题。
    二. 线程的工作方式:
    (1.)先在自己的工作内存中找变量
    (2.)去主内存里面找变量.
    三.线程安全问题解决
    1.CPU抢占式调度(不能解决,因为CPU抢占是不可控的)
    2.每个线程操作自己的变量(可能行)不通用,修改难度大。
    3.在关键代码让所有的CPU排队执行,加锁。
    (1)锁操作的关键步骤:
  3. 尝试获取( 如果成功拿到锁加锁,排队等待)
  4. 释放锁
    四.Java中解决线程安全问题的方案:
  1. synchronized 加锁和释放锁 [ JVM 层面的解决方案,自动帮我们进行加锁和释放锁 ]
  2. Lock手动锁[程序员自己加锁和释放锁]
    五 .synchronized注意事项:
    在进行加锁操作的时候,同一组业务一定是同一个锁对象。
    1.synchronized实现原理:
    (1.)操作:互斥锁mutex
    (2.)JVM中:帮我们实现的监视器锁的加锁和释放锁的操作。
    (3.)在Java层面中:
    a)锁对象mutex .
    b)锁存放的地方:变量的对象头
    2.synchronized说明
    synchronized锁机制是非公平锁。
    公平锁可以按顺序进行执行,而非公平锁执行的效率更高。
    在Java中所有锁默认的策略都是非公平锁。

(1) JDK 1.6之前是使用重量级锁实现的,性能非常低,所以用的并不多。
(2)JDK 1.6对synchronized做了一个优化(锁升级) :
优化过程:无锁—》偏向锁(第一个线程第一次访问)—》轻量级锁(自旋)—》重量级锁(停止自旋,并且把当前没有获取到锁的线程放到等待队列里)
3.synchronized的使用场景(3种) :
1)使用 sylnclchronized 来修饰代码块(加锁对象可以自定义)
2)使用synchronized 来修饰静态方法、( 加锁对象是当前的类对象)

public static synchronized void decrement() {
for(inti=0;i<maxSize;1++){
number--;
}

3)使用synchronized 可以用来修饰普通方法
(加锁对象是当前类的实例)

public synchronized void decrement() {
for(inti=0;i<maxSize;i++){
number-- ;
}
}

六.lock手动锁
Lock默认的锁策略也是非公平锁,但是Lock可以显示的声明为公平锁。

注意事项:一定要把lock()放在try外面
原因:
解决线程不安全问题的三种方法_第1张图片

(1)如果将lock()方法放在try里面,那么当try里面的代码出现异常之后就会执行finally里面的释放锁的代码,但此时加锁还没有成功(2)如果将lock()方法放在try里面,执行finally里面释放锁的代码的时候就会报错(线程状态异常),释放锁的异常会覆盖掉业务代码的异常报错,因此增加了排除错误的成本
Lock只能用来修饰代码块。
七. volatile和synchronized 有什么区别?
A:volatile可以解决内存可见性问题和禁止指令重排序,但volatile不能解决原子性问题; synchronized 用来保证线程安全,也就是synchronized 可以解决任何关于线程安全的问题(关键代码排队执行,始终只有一个线程会执行加锁操作;原子性问题,指定重排序问题,原子性问题 )。
2. synchronized和Lock之间的区别?

  1. synchronized既可以修饰代码块,又可以修饰静态方法或者普通方法;而Lock 只能修
    饰代码块。
  2. synchronized只有非公平锁的锁策略,而Lock既可以是公平锁也可以是非公平锁
    (ReentrantLock默认是非公平锁,也可以通过构造函数设置true 声明它为公平锁)。
  3. ReentrantLock更加的灵活(比如tryLock试图获取锁) 。
  4. synchronized是自动加锁和释放锁的,而ReentrantLock 需要自己手动加锁和释放锁。

你可能感兴趣的:(web,多线程,java)