Java并发编程②

95 阅读7分钟

本节介绍锁、并发关键字、多线程共享数据方法。

image.png Java中每个对象都有个monitor对象,加锁就是竞争monitor,分别加上monitorentermonitorexit指令实现。

分类方式

①乐观锁(基于CAS)、悲观锁(基于AQS)
②共享锁、独占锁
③偏向锁、轻量级锁和重量级锁

自旋锁

用于短时间内可释放锁资源的线程,减少CPU上下文切换时间,但若持有锁时间过长或竞争过于激烈,会引起CPU浪费。关键取决于自旋锁的时间阈值。

乐观锁(基于CAS)

乐观锁预设别人不会修改数据,因此不会加锁,更新数据时会通过版号判断别人是否更新过数据,写时先读出版号(解决ABA问题,线程可感知数据被改写)再加锁。具体流程:比较版号,一致更新,不一致重复进行读、比较和写操作。乐观锁基于CAS。
CAS:比较并交换。多个线程更新变量是,只有一个会胜出,失败的线程仅告知失败并可再次尝试,线程也可放弃更新。
三个参数:V要更新的变量、E预期值、N新值。JDK中的具体实现:

public class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value;
    public final int get() {
        return value;
    }
    public final int getAndIncrement() {
        for(;;){//CAS自旋,一直尝试,直到成功
            int current = get();
            int next = current + 1;
            if(compareAndSet(current, next)) return current;
        }
    }
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

悲观锁(基于AQS)

采用悲观思想处理数据,每次读取都认为数据会被修改,每次读写都会加锁。当其他线程想读取就会阻塞、等待直到获取锁。 Java的悲观锁大多基于AQS(抽象队列同步器),首先尝试以CAS乐观锁去获取,若获取不到则转化为悲观锁(如ReentrantLock)。

其他锁

synchronized

独占悲观锁、可重入(一个线程可对一个资源多次加锁)锁。synchronized修饰对象(方法、代码块)同一时刻只能有一个线程对其访问。
锁非静态方法和成员变量是锁对象,锁静态方法是锁类实例,锁代码块是锁代码块中的配置对象。
内部有①ContentionList(锁竞争队列,包含所有请求锁的线程)、②EntryList(竞争候选队列,ContentionList中有资格成为候选者的线程)、③WaitSet(等待集合)、④OnDeck(竞争候选者,同一时刻只有一个)、⑤Owner(竞争到锁)、⑥!Owner(释放锁后)六个区域。 JDK1.6引入了适应自旋、锁消除、锁粗化、轻量级锁和偏向锁优化synchronized
锁膨胀:偏向锁到轻量级锁再到重量级锁的升级过程,Java中只能单向升级。

ReentrantLock

继承了Lock类,通过自定义队列同步器(AQS)实现一个可重入的独占锁。
支持公平锁(竞争锁机制公平)和非公平锁(不同线程获取锁的机制不公平)的实现,对比synchronized还提供了可响应中断锁、可轮询锁请求、定时锁等可避免死锁的方法。
ReentrantLock需要现实获取和释放锁,API级别,synchronized隐式声明,JVM级别。

synchronizedLock的区别

synchronized:同步阻塞,悲观并非策略,是Java关键字由Java内置语言实现 Lock:同步非阻塞,乐观并发策略,是一个接口,可由Lock得知是否成功获取到锁,synchronized无法实现。可以通过定义读写锁提高多个线程读操作的效率。

读写锁

以锁分离的思想对锁的优化。根据不同应用场景将锁的功能分离,读读不互斥、读写互斥、写写互斥,保证安全性又提高性能。
在Java中通过读写锁接口java.util.concurrent.locks.ReadWriteLock实现类ReentrantReadWriteLock来完成对读写锁定义和使用。具体用法如下:

public class SeafCache {
    private final Map<String, Object> cache = new HashMap<String, Object>;
    private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
    private final Lock readLock = rwlock.readLock();//定义读锁
    private final Lock writeLock = rwlock.writeLock();//定义写锁
    public Object get(String key) {
        readLock.lock();
        try {
            return cache.get(key);
        }finally{
            readLock.unlock();
        }
    }
    public Object put(String key, Object value) {
        writeLock.lock();
        try{
            return cache.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
}

Java并发关键字

AtomicInteger

原子操作同步类,使同步操作(线程安全操作)更加方便、高效。类似的还有AtomicBooleanAtomicLongAtomicReference等。具体用法如下:

image.png

class AtomicIntegerDemo implements Runnable {
    static AtomicInteger safeCounter = new AtomicInteger(0);
    public void run() {
        for(int m = 0; m < 1000000; m++) {
            safeCounter.getAndIncrement();//原子操作数执行自增操作
        }
    }
};

public class AtomicIntegerDemoTest {
    public static void main(String[] args) throws InterruptedException {
        AtomicIntergerDemo mt = new AtomicIntegerDemo();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        t1.start();
        t2.start();
        Thread.sleep(500);
        System.out.println(mt.safeCounter.get());
    }
} 

CountDownLatch

位于java.util.concurrent包下,是一个同步工具类,允许一个或多个线程一起等待其他线程执行完毕再进行相关操作,如主线程等待多个子线程执行完毕的场景。具体实现如下:

final CountDownLatch latch = new CountDownLatch(2);
new Thread() {public void run() {
    try{
        System.out.println("子线程1正在执行");
        Thread.sleep(3000);
        System.out.println("子线程1执行完毕");
        latch.countDown();
    } catch (Exception e) {
        
    }
}}.start();
new Thread() {public void run() {
    try{
        System.out.println("子线程2正在执行");
        Thread.sleep(3000);
        System.out.println("子线程2执行完毕");
        latch.countDown();
    } catch (Exception e) {
        
    }
}}.start();
try {
    System.out.println("等待两个子线程执行完毕...");
    latch.await();
    System.out.println("两个子线程已经执行完毕,继续执行主线程");
} catch (Exception e) {
    e.printStackTrace();
}

CyclicBarrier

CyclicBarrier循环屏障是一个同步工具,实现一组线程等待至某个状态之后再全部同时执行,可重用,运行状态被称为Barrier状态。具体使用方法如下:

public static void main(String[] args) {
    int N = 4;
    CyclicBarrier barrier = new CyclicBarrier(N);//定义CyclicBarrier
    for(int i = 0; i < N; i++) {
        new BusinessThread (barrier).start();
    }
}
//定义业务线程
static class BusinessThread extends Thread {
    private CyclicBarrier CyclicBarrier;
    public BusinessThread (CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }
    @Override
    public void run() {
        try {
            //执行业务逻辑,等待5s
            Thread.sleep(500);
            System.out.println("线程准备工作完成,等待其他线程准备工作完成");
            //业务线程执行完成,等待其他线程成为Barrier状态
            cyclicBarrier.await();
        } catch(InterruptedException e) {
            e.printStackTrace();
        } catch(BrokenBarrierException e) {
            e.printStackTrace();
        }
        //所有线程均进入Barrier状态
        System.out.println("所有线程准备工作均完成,执行下一项任务");
    }
}

Semaphore

信号量,控制同时访问某些资源的线程个数,通过acquire()获取许可,用完使用release()释放许可。如下为5个员工使用两个打印机的实现:

int printNumber = 5;
Semaphore semaphore = new Semaphore(2);
for(int i = 0; i < printNumber; i++) {
    new Worker(i,semaphore).start();
}
static class Worker extends Thread {
    private int num;
    private Semaphore semaphore;
    public Worker(int num, Semaphore semaphore) {
        this.num = num;
        this.semaphore = semaphore;
    }
    @Override
    public void run() {
        try{
            semaphore.acquire();
            System.out.println("员工"+ this.num +"占用一个打印机");
            Thread.sleep(2000);
            System.out.println("员工"+ this.num +"打印完毕,释放资源");
            Thread.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

共享数据

关注:可见性、原子性、有序性。Java内存模型保障了可见性和有序性,锁保障了原子性。
常见的共享数据的方式为将数据抽象成类;将Runnable对象作为一个类的内部类,共享数据为成员变量。

将数据抽象为类

只需添加synchronized关键字即可,具体实现如下:

public class MyData {
//将数据抽象为MyData类,并提供add、dec方法
    private int j = 0;
    public synchronized void add() {
        j++;
        System.out.println("线程"+ Thread.currentThread().getName() + "j为:" + j);
    }
    public synchronized void dec() {
        j--;
        System.out.println("线程"+ Thread.currentThread().getName() + "j为:" + j);
    }
    public int getData() {
        return j;
    }
}
public class AddRunnable implements Runnable {
    MyData data;
    public AddRunnable (MyData data) {
        this.data = data;
    }
    public void run() {
        data.dec();
    }
}
public class DecRunnable implements Runnable {
    MyData data;
    public DecRunnable (MyData data) {
        this.data = data;
    }
    public void run() {
        data.dec();
    }
}
public static void main(String[] args) {
    MyData data = new MyData();
    Runnable add = new AddRunnable(data);
    Runnable dec = new DecRunnable(data);
    for(int i = 0; i < 2; i++) {
        new Thread(add).start();
        new Thread(dec).start();
    }
}

Runnable对象作为一个类的内部类,共享数据作为成员变量

public class MyData {
//将数据抽象为MyData类,并提供add、dec方法
    private int j = 0;
    public synchronized void add() {
        j++;
        System.out.println("线程"+ Thread.currentThread().getName() + "j为:" + j);
    }
    public synchronized void dec() {
        j--;
        System.out.println("线程"+ Thread.currentThread().getName() + "j为:" + j);
    }
    public int getData() {
        return j;
    }
}
public class TestThread {
    public static void main(String[] args) {
        final MyData data = new MyData();
        for(int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                public void run(){
                    data.add();
                }
            }).start();
            new Thread(new Runnable() {
                public void run(){
                    data.dec();
                }
            }).start();
        }
    }
}