Java中提供并发控制的两种方式:1、同步关键字 2、锁
Java 5.0之前使用的是同步关键字Synchronized和volatile,他们是jvm中的隐式锁
Synchronized和volatile的实现是基于jvm指令的同步代码块实现的。
添加同步关键字后,会在jvm代码块指令前后添加monitorexit和monitorenter两个同步指令。
但是Synchronized修饰的方法却不一样,同步方法的实现是JVM定义了方法的访问标志ACC_SYNCHRONIZED在方法表中,JVM将同步方法前面设置这个标志。
同步代码块和同步方法的字节码指令(javap -c classname)
public void syncBlockImpl();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #23 // String hello world
9: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
public synchronized void syncMethodImpl();
Code:
0: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #23 // String hello world
5: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
Java 5.0之后,JDK添加了多线程并发库java.util.concurrent,提供了线程池(Executors)、信号量(Semaphore)、重入锁(ReentrantLock)、读写锁(ReentrantWriteReadLock)和一些线程安全的集合类(ConcurrentHashMap/ConcurrentLinkedQueue)、CountDownLatch
无论是Semaphore还是ReentrantLock都是基于AbstractQueueSynchronized实现的,AQS实现了自己的算法来实现共享资源的合理控制。AQS中核心的变量替换方法(原子性)是借助CPU硬件指令集compareAndSweep实现的,等待队列是由虚拟无界双向链表CLH实现的。每一个等待线程对一个Node,Node中存储了当前线程引用、等待状态、前一个或后一个等待节点。
CLH锁,是一种基于链表的可扩展、高性能、公平的自旋锁。
锁分为公平锁和协商锁(非公平锁),其中公平锁的开销要大。
一、信号量Semaphore的用法
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(5, false);//非公平信号量
//开启20个线程
for (int i = 0; i < 20; i++) {
final int Num = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
//线程开始时,获取许可
semaphore.acquire();
System.out.println(“Thread ” + Num + ” accessing”);
Thread.sleep((long) (Math.random() * 10000));
//访问完后释放
semaphore.release();
System.out.println(“available permit = ” + semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.submit(runnable);
}
Semaphore本质为共享锁,用来限制多个线程对有限资源访问进行控制。
二、ReentrantLock 可重入锁 独占锁
比Synchronized要高效,因为在高度争用情况下,处理器把大部分时间都花在任务处理上,而不是线程调度上
特性:时间等候锁,可中断锁,多个条件变量,锁投票,无块结构锁
一般用法:
try {
reentrantLock.lock();
System.out.println(str + ” 获取锁 “);
doSomeThing(str);
Thread.sleep((long) (Math.random() * 10000));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(str + ” 释放锁 “);
reentrantLock.unlock();
}
时间等候锁,在一定时间内获取不到锁,则返回false
boolean captured = false;
try {
//在2秒内尝试获取锁
captured = reentrantLock.tryLock(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(“tryLock(10, TimeUnit.SECONDS) ” + captured);
} finally {
if (captured) {
reentrantLock.unlock();
}
}
可中断锁,获取锁的线程可被自己或其他线程中断
try {
reentrantLock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + ” 获取锁 “);
try {
Thread.sleep(5 * 1000);
reentrantLockTest.plusMethod();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + “被中断 “);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
System.out.println(Thread.currentThread().getName() + ” 释放锁 “);
}
多条件变/
* 生产者-消费者
* 多条件锁
*
* Created by zczhang on 16/9/27.
*/
public class ProductQueue {
private T[] items;
private final Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
private int head, tail, count;
public ProductQueue() {
this(10);
}
public ProductQueue(int maxSize) {
items = (T[]) new Object[maxSize];
}
public void put(T t) throws InterruptedException {
lock.lock();
try {
while (count == getCapacity()) {
System.out.println("---------队列满,生产者等待------");
notFull.await();
}
items[tail] = t;
if (++tail == getCapacity()) {
tail = 0;
}
++count;
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
System.out.println("---------队列空,消费者" + Thread.currentThread().getName() + "等待------");
notEmpty.await();
}
T item = items[head];
items[head] = null;
if (++head == getCapacity()) {
head = 0;
}
--count;
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public int getCapacity() {
return items.length;
}
}eentrantReadWriteLock读写锁
读锁为共享锁,写锁为独占锁。读读不互斥,读写互斥,写写互斥
写锁支持Condition,读锁不支持Condition。
支持锁降级,即写锁可以获取读锁,然后释放写锁,这样写锁就变成了读锁
不支持锁升级,即读锁不能直接获取写锁。需将读锁释放后再获取写锁。
支持锁中断
读写锁的最大数量只能是65535(包括重入数)
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private int resource = 0;
public int read() {
try {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
readLock.lock();
System.out.println(Thread.currentThread().getName() + "获取读锁,尝试读取");
return resource;
} finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放读锁");
}
}
public void write(int newValue) {
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "获取写锁,尝试写入");
this.resource = newValue;
System.out.println(Thread.currentThread().getName() + "写入值 " + newValue);
} finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
}
}
四、CountDownLatch
/**
* CountDownLatch 同步辅助类
* 指定数字count初始化,然后调用await方法的线程会一直阻塞,直到count变为0
* <p>
* 一个同步辅助类,允许一个或多个线程等待,一直到其他线程完成任务
* 使用场景:1.主线程开启多个子线程去执行分解的任务,当所有子线程都完成后,主线程再继续执行。
* 2.主线程先运行,子线程再运行,主线程等待子线程完成再运行
* <p>
* Created by zczhang on 16/9/28.
*/
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
int childTaskNum = 3;
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch childEndLatch = new CountDownLatch(childTaskNum);
for (int i = 0; i < childTaskNum; i++) {
Thread child = new Thread(new ChildTaskRun(startLatch,childEndLatch));
child.start();
}
System.out.println("主线程处理一些任务...");
Thread.sleep(2*1000);
System.out.println("主线程处理一些任务完成");
startLatch.countDown();
System.out.println("主线程等待子线程处理任务完成...");
childEndLatch.await();
System.out.println("主线程处理任务结果");
}
private static class ChildTaskRun implements Runnable {
private CountDownLatch mainStartLatch;
private CountDownLatch childEndLatch;
public ChildTaskRun(CountDownLatch mainStartLatch, CountDownLatch childEndLatch) {
this.mainStartLatch = mainStartLatch;
this.childEndLatch = childEndLatch;
}
@Override
public void run() {
try {
mainStartLatch.await();
doSomeThing();
System.out.println("子线程 " + Thread.currentThread().getName() + "处理任务完成");
childEndLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void doSomeThing() {
System.out.println("子线程 " + Thread.currentThread().getName() + "处理任务中");
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
五、CyclicBarrier
/**
* 回环栅栏 同步辅助类
* 可实现让一组线程等待至某个状态之后再全部同时执行,这个状态就叫做一个栅栏.
* 当所有线程都被释放后,该栅栏可以重用
* <p/>
* 使用场景:开启多个子线程处理同步任务,当所有子任务都处理完成后,各个子线程再处理其他任务.
* 同时主线程可插入任务,当最后一个子线程完成同步任务后,执行主线程插入任务,然后子线程再处理其他任务
* <p/>
* Created by zczhang on 16/9/28.
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
int childTaskNum = 3;
CyclicBarrier cyclicBarrier = new CyclicBarrier(childTaskNum, new Runnable() {
@Override
public void run() {
System.out.println("当最后一个线程 " + Thread.currentThread().getName() + "完成同步任务时,做一些事情...");
}
});
for (int i = 0; i < childTaskNum; i++) {
Thread thread = new Thread(new ChildTaskRun(cyclicBarrier));
thread.start();
}
}
public static class ChildTaskRun implements Runnable {
private CyclicBarrier cyclicBarrier;
public ChildTaskRun(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("子线程" + Thread.currentThread().getName() + "开始处理任务...");
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程" + Thread.currentThread().getName() + "处理任务完成,等待其他线程...");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("所有子线程完成同步任务, 继续执行其他任务...");
}
}
}