3.1重入锁
重入锁使用java.util.concurrent.locks.ReentrantLock来实现
public class Test implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
lock.lock();
try {
i++;
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws Exception {
Test t1 = new Test();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t1);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(i);
}
}
重入锁与synchronized相比有明显的操作过程,开发人员必须指定何时加锁,何时解锁。同时,重入锁是可以反复添加的。一个县城可以连续两次获得同一把锁。
lock.lock();
lock.lock();
try {
i++;
} finally {
lock.unlock();
lock.unlock();
}
3.1.1重入锁可以被中断
public class Test implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
/**
* 控制加锁顺序,方便构成死锁
*
* @param lock
*/
public Test(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
lock1.lockInterruptibly(); // 以可以响应中断的方式加锁
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly(); // 以可以响应中断的方式加锁
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.err.println(Thread.currentThread().getId() + "退出!");
}
}
public static void main(String[] args) throws Exception {
Test t1 = new Test(1);
Test t2 = new Test(2);
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
System.out.println("thread1: "+thread1.getId());
System.out.println("thread2: "+thread2.getId());
thread1.start();
thread2.start();
Thread.sleep(1000);
thread2.interrupt();//③给t2线程状态标记为中断
}
}
t1、t2线程开始运行时,会分别持有lock1和lock2而请求lock2和lock1,这样就发生了死锁。但是,在③处给t2线程状态标记为中断后,持有重入锁lock2的线程t2会响应中断,并不再继续等待lock1,同时释放了其原本持有的lock2,这样t1获取到了lock2,正常执行完成。t2也会退出,但只是释放了资源并没有完成工作。
3.1.2锁申请等待限时
可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。
前者不带参数,这时线程尝试获取锁,如果获取到锁则继续执行,如果锁被其他线程持有,则立即返回 false ,也就是不会使当前线程等待,所以不会产生死锁。
后者带有参数,表示在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false。
public class Test implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
}else {
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
}
tryLock()方法接受的两个参数,一个表示等待时长,另一个表示计时单位。在本例中,由于占用锁的进程持有锁长达6秒,另一个线程无法再5秒的等待时间中得到锁,会返回false。
3.2公平锁
在大多数情况下,锁的申请并不是公平的,先申请的线程并不一定在锁可用时先使用。要实现公平锁需要系统维护一个有序数列,性能较低,因此,默认情况下,锁都是非公平的。
public class Test implements Runnable {
/**
* 默认为false,表示为非公平锁
*/
public static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"获得锁");
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
}
}
两个线程会交替输出。如果是非公平锁:
线程1显示一大堆,然后线程2显示一大堆。一个线程会倾向于再次获取已经持有的锁。
3.3Condition条件
await()方法会使当前线程等待,同时释放当前锁,当其他线程使用signal()或者signalAll()方法时,线程会重新获得锁并执行。
public class Test implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
condition.await();
System.out.println("Thread is going on");
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException{
Test test = new Test();
Thread t1 = new Thread(test);
t1.start();
Thread.sleep(2000);
//通知线程继续执行
lock.lock();
condition.signal();
lock.unlock();
}
}
3.4信号量(Semaphore)
信号量可以指导多个线程同时访问某一资源。
acquire()方法尝试获取一个准入许可,若无法获得,则线程等待。
tryAcquire()方法尝试获取一个许可,成功返回true,失败返回false,不会等待。
release()用于线程访问完资源后,释放许可。
public class Test implements Runnable {
final Semaphore semaphore = new Semaphore(5);
@Override
public void run() {
try {
semaphore.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+" done");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException{
ExecutorService executorService = Executors.newFixedThreadPool(20);
final Test test = new Test();
for (int i = 0;i<20;i++){
executorService.submit(test);
}
}
}
5个线程为一组同时访问资源,但线程的顺序随机。
3.5ReadWriteLock读写锁
读写锁允许多个线程同时读,但写写操作和读写操作间依然需要相互等待及持有锁。
public class Test {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
// 模拟读操作
public Object handleRead(Lock lock)throws InterruptedException{
try {
lock.lock();
// 读操作耗时越多,读写锁的优势越明显
Thread.sleep(1000);
return value;
} finally {
lock.unlock();
}
}
// 模拟写操作
public void handleWrite(Lock lock,int index) throws InterruptedException{
try {
lock.lock();
Thread.sleep(1000);
value = index;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final Test test = new Test();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
try {
test.handleRead(readLock);
// test.handleRead(lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnable = new Runnable() {
@Override
public void run() {
try {
test.handleWrite(writeLock,new Random().nextInt());
// test.handleWrite(lock,new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0;i<18;i++){
new Thread(readRunnable).start();
}
for (int i = 18;i<20;i++){
new Thread(writeRunnable).start();
}
}
}
读线程和写线程可以并行,写会阻塞读,所以这段代码运行2秒多就结束,如果不用读写锁,则需要20多秒。
3.6倒计时器:CountDownLatch
例如火箭发射倒计时,完成10项检查才能发射火箭。
public class Test implements Runnable {
// 标明要完成10个任务
static final CountDownLatch end = new CountDownLatch(10);
static final Test test = new Test();
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println("check template");
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException{
// 创建10个任务
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
service.submit(test);
}
// 等待检查,要求主线程完成所有任务后才能执行
end.await();
System.out.println("Fire!");
service.shutdown();
}
}
3.7循环栅栏:CyclicBarrier
假设司令下达命令,要求10个士兵一起去完成一项任务。士兵需要先集合,再去完成任务。
public class Test {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclic;
Soldier(CyclicBarrier cyclic, String soldierName) {
this.cyclic = cyclic;
this.soldier = soldierName;
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt() % 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + " 任务完成");
}
@Override
public void run() {
try {
// 确定是否都集合完毕
cyclic.await();
doWork();
// 确定是否都完成工作
cyclic.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
@Override
public void run() {
if (flag) {
System.out.println("司令:[士兵" + N + "个,任务完成]");
} else {
System.out.println("司令:[士兵" + N + "个,集合完毕]");
flag = true;
}
}
}
public static void main(String[] args) {
final int N = 10;
Thread[] allSoldier = new Thread[N];
boolean flag = false;
//循环调用这个计时器
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合队伍!");
for (int i = 0; i < N; i++) {
System.out.println("士兵" + i + "报道");
allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));
allSoldier[i].start();
}
}
}
3.8线程阻塞工具类:LockSupport
LockSupport可以在线程中的任何位置让线程阻塞。
public class Test {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread{
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run(){
synchronized (u){
System.out.println("in "+getName());
LockSupport.park();
}
}
}
public static void main(String[] args) throws InterruptedException{
t1.start();
Thread.sleep(100);
t2.start();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}