关于java:Java线程同步机制

38次阅读

共计 3273 个字符,预计需要花费 9 分钟才能阅读完成。

线程同步机制就是保障线程平安、协调线程间接数据拜访的机制。
Java 提供的线程同步机制包含:

  • volatile 关键字
  • final 关键字
  • static 关键字
  • 相干 API(如 Object.wait() 等)
  • ……

锁保障线程平安的思路为:将多个线程对共享数据的并发拜访转换为串行拜访,即一个共享数据一次只能被一个线程拜访。

咱们平时听到用到的锁有很多种:偏心锁 / 非偏心锁、可重入锁 / 不可重入锁、共享锁 / 排他锁、乐观锁 / 乐观锁、分段锁、偏差锁 / 轻量级锁 / 重量级锁、自旋锁。其实这些都是在不同维度或者锁优化角度对锁的一种叫法。

JVM 把锁分为隐式锁(外部锁)和显示锁两种。所谓的显示和隐式,就是指使用者应用时要不要手动写代码去获取锁和开释锁的操作。

  • 隐式锁 / 外部锁:通过 synchronized 关键字实现。
  • 显示锁:通过 Lock 接口的实现类实现。次要有 ReentrantLock、StampedLock、ReadWriteLock、ReentrantReadWriteLock 等。

在应用 synchronized 关键字时,咱们不必写其余的代码程序就可能获取锁和开释锁,那是因为当 synchronized 代码块执行实现后,零碎会主动让程序开释占用的锁,这是由系统维护的,如果非逻辑问题的话,是不会呈现死锁的。

在应用 Lock 时,咱们须要手动的获取和开释锁。如果没有开释锁,就有可能导致呈现死锁。

synchronized(隐式锁)

synchronized 能够润饰代码块或办法。

集体了解,锁住同一个变量的代码块或办法共享同一把锁,而同一时刻,共享了同一把锁的代码块或办法只能被一个线程拜访。只有明确了这一点,所谓办法锁(其实也是对象锁)、对象锁、类锁就很容易了解了。

  • 同步代码块

    //1. 锁住非动态变量。锁住同一个变量的代码块共享同一把锁。非动态变量不被其余线程共享,所以多个实例调用同一办法也不会受这个锁的影响。private Object lock = new Object();
    public void method() {synchronized (lock) {//code...}
    }
    
    //2. 锁住 this 对象。所有锁住 this 的代码块共享同一把锁。同样多个实例调用这些办法也不会受锁的影响。synchronized (this) {//code...}
    
    //3. 锁住动态变量。因为动态变量在 JVM 办法区只有一份,而办法区被所有线程共享,因而锁住动态变量相当于类锁。private static Object lock = new Object();
    public void method() {synchronized (lock) {//code...}
    }
    
    //4. 锁住 xxx.class。即类锁,类锁是所有线程共享的。synchronized (XXX.class) {//code...}
  • 同步办法

    //1. 锁住非静态方法。相当于锁对象是 this。public synchronized void method() {//code...}
    
    //2. 锁住静态方法。相当于类锁。public static synchronized void method() {//code...}

Lock(显式锁)

synchronized 关键字的锁是 JVM 层实现的。JDK5 之后在 java.util.concurrentjava.util.concurrent.locks 包里有了显式锁,即 Lock 和 ReadWriteLock 两个接口。常见实现类有:

  • ReentrantLock(Lock 的实现类)
  • ReentrantReadWriteLock(ReadWriteLock 的实现类)

Lock 接口 有以下办法:

// 获取锁
void lock()

// 如果以后线程未被中断,则获取锁,能够响应中断
void lockInterruptibly()

// 返回绑定到此 Lock 实例的新 Condition 实例
Condition newCondition()

// 仅在调用时锁为闲暇状态才获取该锁,能够响应中断
boolean tryLock()

// 如果锁在给定的等待时间内闲暇,并且以后线程未被中断,则获取锁
boolean tryLock(long time, TimeUnit unit)

// 开释锁
void unlock()

ReadWriteLock 接口 有两个办法:

// 返回用于读取操作的锁  
Lock readLock()

// 返回用于写入操作的锁  
Lock writeLock() 

ReentrantLock

lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用来获取锁的。unLock()办法是用来开释锁的。newCondition() 返回 绑定到此 Lock 的新的 Condition 实例,用于线程间的合作。

其中,这四种获取锁的办法有什么区别?参考《Java 锁 Lock 接口详解》一文。lock()和 unlock()的应用办法如下:

Lock lock = new ReentrantLock();
lock.lock();// 获取锁
try{// 解决工作}catch(Exception ex){

}finally{lock.unlock();   // 开释锁
}

ReentrantReadWriteLock

ReadWriteLock 保护了一对相干的锁,一个用于只读操作,另一个用于写入操作。只有没有 writer,读取锁能够由多个 reader 线程同时放弃,而写入锁是独占的。

private Object data = null;  // 共享数据,只能有一个线程能写该数据,但能够有多个线程同时读该数据。ReadWriteLock lock = new ReentrantReadWriteLock();

// 读数据
public void get() {lock.readLock().lock();  // 加读锁
    try {// 解决工作} catch (InterruptedException e) {e.printStackTrace();
    } finally {lock.readLock().unlock();  // 开释读锁}
}

// 写数据
public void put(Object data) {lock.writeLock().lock();  // 加写锁
    try {// 解决工作} catch (InterruptedException e) {e.printStackTrace();
    } finally {lock.writeLock().unlock();  // 开释写锁}
}

锁的类型

锁 / 类型 偏心 / 非偏心锁 可重入 / 不可重入锁 共享 / 独享锁 乐观 / 乐观锁
synchronized 非偏心锁 可重入锁 独享锁 乐观锁
ReentrantLock 都反对 可重入锁 独享锁 乐观锁
ReentrantReadWriteLock 都反对 可重入锁 读锁 - 共享,写锁 - 独享 乐观锁

偏心锁和非偏心锁、可重入锁和不可重入锁、共享锁和独享锁、乐观锁和乐观锁有什么区别?可参考《Java 锁 Lock 的品种》一文。

volatile

volatile 关键字是线程同步的轻量级实现,性能比 synchronized 要好。volatile 只能润饰变量,能够解决变量在多个线程的可见性。

应用办法如下:

private static volatile int count;

其余线程同步机制

除了锁机制和 volatile 关键字,Java 中还有一些实现线程同步的办法。

  • final关键字

    其原理是通过禁止 CPU 的指令集重排序来提供线程的可见性,从而保障线程同步。

    当一个对象公布到其余线程的时候,该对象的所有 final 字段都初始化实现,即其余线程读取到的都是相应字段的初始值而非默认值。

  • static关键字

    即便在未应用其余线程同步机制的状况下,static 关键字保障一个线程能够读到一个类动态变量的初始值,但这种可见性仅限于首次读取该变量。

参考

多线程和高并发(二)volatile 关键字

Java 锁 Lock 的品种

java 里的锁总结(这篇讲得过于深刻)

Java 并发之显式锁和隐式锁的区别

对象锁和类锁的区别

java 锁 Lock 接口详解

正文完
 0