本节介绍锁、并发关键字、多线程共享数据方法。
锁
Java中每个对象都有个
monitor对象,加锁就是竞争monitor,分别加上monitorenter和monitorexit指令实现。
分类方式
①乐观锁(基于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级别。
synchronized与Lock的区别
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
原子操作同步类,使同步操作(线程安全操作)更加方便、高效。类似的还有AtomicBoolean、AtomicLong、AtomicReference等。具体用法如下:
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();
}
}
}