多线程开发离不开各种锁,下面总结下Java和JDK提供的各种锁机制
synchronized
synchronized是java语言中提供的一个关键字,其作用是给一个代码块加锁,它有两种使用方法
- 给一个代码块加锁
被synchronized包围的代码块不能被多个线程同时执行。用法如下
synchronized (object) {
// code
}
例如,下面的Counter类中,被 guard所保护的两段代码不能被多线程重入
public class Counter {
private Object guard= new Object();
private int count = 0;
private void increase() {
log("increase in");
synchronized (countGuard) {
count++;
}
}
private void decrease() {
log("decrease in");
synchronized (countGuard) {
count--;
}
}
}
- 给一个函数加锁
synchronized 还可以修饰一个函数,其作用等同于给这个函数中所有的代码加锁,用法如下:
synchronized private void f() {
// code
}
上面的写法等同于下面这段代码
private void f() {
synchronized (this) {
// code
}
}
Object中的wait/notify/notifyAll方法
java通过Object对象的wait/notify方法提供了线程间的协作方式,这三个方法的作用如下
method | 作用 | 说明 |
---|---|---|
wait | 让当前线程进入等待知道notify/notifyAll被调用 | wait支持传入一个时间来指定等待的最长时间 |
notify | 唤醒某一个等待在该对象上的线程 | 如果有多个线程在等待,则任意唤醒一个 |
notifyAll | 唤醒所有等待在该对象上的线程 | NA |
需要注意,在使用这三个方法之前必须先确保当前线程已经获取了该对象的监视器,获取某个对象的监视器有如下三种方式
- 执行这个对象的被synchronized修饰的方法
- 执行一个使用synchronized同步在这个对象上的代码块
- 对于Class类型的对象,执行这个类的被synchronized修饰的静态方法
一个对象的监视器同时只能被一个线程占用
Lock & Condition
Lock是JDK中提供的一个抽象类,它有多种实现,例如ReentrantLock。下面只讨论Lock提供的功能
- lock & unlock
Lock提供了加锁和释放锁的功能,相关方法的作用如下:
method | 作用 | 说明 |
---|---|---|
lock | 请求获取锁 | 如果能获取锁,则继续执行,否则该线程陷入等待状态 |
unlock | 释放锁 | 这个函数调用后,其他请求这个锁的线程会得到执行的机会 |
tryLock | 尝试获取锁 | 尝试获取锁并返回,如果能获取到,则返回true,否则返回false,这个函数还可以加上一个等待时间 |
- Condition
Condition是JDK提供的一种类似wait/notify的,但是更加便于使用。与wait/notify相同,Condition在使用之前,也必须先获取对应的锁。示例代码如下,显然有多个同步条件时,Lock机制远比Object的wait/notify便捷。下面是一个使用lock和condition的例子
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
ReentrantLock
ReentrantLock是Lock的子类,除了上面描述的功能外,它还提供了其他一些功能,其中最重要的就是公平锁和非公平锁
所谓公平锁,简单来说就是先加锁的线程有限获得执行机会;非公平锁含义相关,当有人释放锁时,系统将随意从加锁的线程中取出一个执行。公平锁是以牺牲性能为代价的。
Semaphore
信号量主要用来控制1个或多个共享资源的使用,支持公平锁,它主要的方法如下
method | 作用 | 说明 |
---|---|---|
acquire | 请求一个/多个资源 | 这个方法还有带timeout和非阻塞的版本 |
release | 释放一个/多个资源 | NA |
Semaphore有很多使用场景,例如下面的代码实现了一个简单资源池的管理
private static class ConnectionPool {
private int mMaxCount;
private Semaphore mSemaphore;
ConnectionPool(int maxCount) {
mMaxCount = maxCount;
mSemaphore = new Semaphore(mMaxCount);
}
Connection acquire() {
try {
mSemaphore.acquire();
return new Connection();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
void release(Connection connection) {
connection.release();
mSemaphore.release();
}
}
CountDownLatch
当一个工作需要等待其他前驱工作完成后才能开展的场景,可以使用CountDownLatch。
其重要的方法如下
method | 作用 | 说明 |
---|---|---|
countDown | 将计数器减一 | 表示等待的一项工作已经完成 |
await | 调用线程进入等待状态,直到计数器为0 | NA |
例如下面的这个场景,主线程中的工作要等待多个子线程完成后才能继续执行,使用CountDownLatch就比较合适
import com.yezi.Util;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTester implements Tester {
private static final String TAG = "CountDownLatchTester";
private static int[][] MATRIX = {
{1, 2, 3},
{4, 9, 6},
{7, 8, 9}
};
private int[] mResult = new int[MATRIX.length];
private class AddArray implements Runnable {
private int mIdx;
AddArray(int idx) {
mIdx = idx;
}
@Override
public void run() {
mResult[mIdx] = sumIntArray(MATRIX[mIdx]);
mCountDownLatch.countDown();
}
}
CountDownLatch mCountDownLatch = new CountDownLatch(MATRIX.length);
private int sumIntArray(int[] array) {
int result = 0;
for (int element: array) {
result += element;
}
return result;
}
@Override
public void run() {
for (int i = 0; i< MATRIX.length; i++) {
new Thread(new AddArray(i)).start();
}
try {
mCountDownLatch.await();
Util.log(TAG, "the sum of MATRIX is " + sumIntArray(mResult));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CyclicBarrier
CyclicBarrier的功能与CountDownLatch很类似,都是用来处理某一个工作需要等待另外一个前驱工作完成后才能开展的情况。与CountDownLatch 不同的是,在使用CyclicBarrier时一个前驱工作完成后,其线程会陷入等待,而当所有的前驱工作都完成时,CyclicBarrier会执行预先指定的任务(可选),该任务执行完成后,之前陷入等待的所有线程才会被唤醒继续执行。
其重要的方法如下
method | 作用 | 说明 |
---|---|---|
await | 当前线程陷入等待,直到最后一个线程达到并执行完预先指定的action | NA |
可以使用CyclicBarrier 改写CountDownLatch中的例子,代码如下
import com.yezi.Util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTester implements Tester {
private static final String TAG = "CyclicBarrierTester";
private static int[][] MATRIX = {
{1, 2, 3},
{4, 9, 6},
{7, 8, 9}
};
private int[] mResult = new int[MATRIX.length];
private class AddArray implements Runnable {
private int mIdx;
AddArray(int idx) {
mIdx = idx;
}
@Override
public void run() {
mResult[mIdx] = sumIntArray(MATRIX[mIdx]);
try {
mCyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
Util.log(TAG, "finish");
}
}
private CyclicBarrier mCyclicBarrier = new CyclicBarrier(MATRIX.length, () -> Util.log(TAG, "the sum of MATRIX is " + sumIntArray(mResult)));
private int sumIntArray(int[] array) {
int result = 0;
for (int element: array) {
result += element;
}
return result;
}
@Override
public void run() {
List threads = new ArrayList<>(MATRIX.length);
for (int i = 0; i< MATRIX.length; i++) {
Thread t = new Thread(new AddArray(i));
threads.add(t);
t.start();
}
for (Thread t: threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}