写在前面: 本文介绍了JUC
中的几种锁以及使用案例。
ReentrantLock
和synchronized
一样,ReentrantLock
也是可重入锁,即同一个线程可以对同一把锁锁定多次。ReentrantLock
是可以替代synchronized
的,只需要将原来写 synchronized
的地方换写lock.lock()
,最后通过lock.unlock()
解锁。
public class TestReentrantLock {
Lock lock = new ReentrantLock();
Integer count = 0;
void m1() {
synchronized (this) {
for (int i = 0; i < 100; i++) {
count ++;
}
}
}
void m2() {
try {
lock.lock();
for (int i = 0; i < 100; i++) {
count ++;
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestReentrantLock rl = new TestReentrantLock();
new Thread(rl::m1).start();
new Thread(rl::m2).start();
}
}
相比synchronized
而言,reentrantlock
还有tryLock
方法,即尝试锁定。该方法返回锁定结果,并且不管锁定与否,后续方法都将继续执行。tryLock
还支持等待时间参数,等待时间内如果拿到锁则立即执行后续代码,拿不到锁但是等待时间结束也会立即执行后续代码。
public class TestReentrantLock2 {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void m2() {
boolean locked = false;
try {
locked = lock.tryLock();
System.out.println(Thread.currentThread().getName() + " " + locked);
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) {
lock.unlock();
}
}
}
void m3() {
boolean locked = false;
try {
locked = lock.tryLock(3, TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName() + " " + locked);
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) {
lock.unlock();
}
}
}
public static void main(String[] args) {
TestReentrantLock2 rl = new TestReentrantLock2();
new Thread(rl::m1, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(rl::m2, "t2").start();
new Thread(rl::m3, "t3").start();
}
}
reentrantlock
还可以使用lockInterruptibly()
方法对interrupt()
做出响应,即可以被打断的加锁。
public class TestReentrantLock3 {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lock();
System.out.println("t1 start");
TimeUnit.SECONDS.sleep(5);
System.out.println("t1 end");
} catch (InterruptedException e) {
System.out.println("interrupted!");
} finally {
lock.unlock();
}
});
t1.start();
Thread t2 = new Thread(() -> {
boolean flag = false;
try {
lock.lockInterruptibly();
flag = true;
System.out.println("t2 start");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end");
} catch (InterruptedException e) {
System.out.println("interrupted!");
} finally {
if (flag) {
lock.unlock();
}
}
});
t2.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();
}
}
上面程序中的lock.lockInterruptibly()
不同于lock.lock()
,在加锁过程中是可以被打断的,即在调用t2.interrupt()
时如果还没拿到锁,t2
线程就会被打断并抛异常。
ReentrantLock
还可以指定为公平锁,公平锁的意思是抢占锁时会有队列来维护谁先谁后,而不是说谁后来了之后就马上让谁执行。如果说这个锁不是公平锁,那么来了一个新线程上来就抢锁是有可能抢到的;如果说这个锁是个公平锁,这个线程上来会先检查队列里有没有原来等着的线程,如果有的话他就先进队列里等着别人先运行。ReentrantLock
默认是非公平锁,公平锁需要通过new ReentrantLock(true)
来指定。
public class TestReentrantLock4 extends Thread {
private static ReentrantLock lock = new ReentrantLock(true);
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获得锁");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
TestReentrantLock4 rl = new TestReentrantLock4();
Thread th1 = new Thread(rl);
Thread th2 = new Thread(rl);
th1.start();
th2.start();
}
}
可以看到公平锁的情况下,两个线程交替输出。而非公平锁的情况下就不一定了。
CountDownLatch
CountDownLatch
就是倒数几个数然后打开门闩的意思。比如现在有10个线程,其中两个线程被latch.await()
阻塞住,其他8个线程倒数8个数,每次通过latch.countDown()
来倒数。倒数结束后两个被latch.await()
阻塞的线程就会继续运行。
public class TestCountDownLatch {
public static void main(String[] args) {
usingCountDownLatch();
}
private static void usingCountDownLatch() {
Thread[] threads = new Thread[10];
CountDownLatch latch = new CountDownLatch(8);
threads[0] = new Thread(() -> {
try {
latch.await();
System.out.println(Thread.currentThread().getName() + "await");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threads[1] = new Thread(() -> {
try {
latch.await();
System.out.println(Thread.currentThread().getName() + "await");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
for (int i = 2; i < 10; i++) {
threads[i] = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " countdown");
latch.countDown();
});
}
for (Thread thread : threads) {
thread.start();
}
System.out.println("end latch");
}
}
可以考虑用join
来实现,但是join
必须等待线程结束后才能继续运行,CountDownLatch
相比之下更灵活。
CyclicBarrier
CyclicBarrier
即循环屏障,加入设定屏障容量为20,当等待的线程达到20了那么这20个线程开始同时运行。接着屏障继续立起来等待后面20个线程满了再继续同时运行,以此类推。
public class TestCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
可以看到“满人”输出了5次,即屏障生效了5次,每次都是20个线程同时运行。
Phaser
Phaser
像是结合了CountDownLatch
和CyclicBarrier
,它可以将线程工作分为多个阶段,每个阶段可以让部分线程往前走,让部分线程停止。具体就是调用phaser.arriveAndAwaitAdvance()
让线程停止,等到等待线程满了再继续执行。还可以调用CyclicBarrier
增加屏障个数。下面是个简单的例子方便理解:
public class TestPhaser {
static MarriagePhaser phaser = new MarriagePhaser();
static void milliSleep(int milli) {
try {
TimeUnit.MILLISECONDS.sleep(milli);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
phaser.bulkRegister(6);
for (int i = 0; i < 5; i++) {
new Thread(new Person("考生" + i)).start();
}
new Thread(new Person("考官")).start();
}
static class MarriagePhaser extends Phaser {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("所有人到达现场!" + registeredParties);
System.out.println();
return false;
case 1:
System.out.println("准备考试!" + registeredParties);
System.out.println();
return false;
case 2:
System.out.println("考试中!" + registeredParties);
System.out.println();
return false;
case 3:
System.out.println("考试结束!" + registeredParties);
System.out.println();
return false;
case 4:
System.out.println("所有人离开" + registeredParties);
return false;
case 5:
System.out.println("测试register" + registeredParties);
return true;
default:
return true;
}
}
}
static class Person implements Runnable {
String name;
public Person(String name) {
this.name = name;
}
@Override
public void run() {
milliSleep(1000);
System.out.printf("%s 到达现场!\n", name);
phaser.arriveAndAwaitAdvance();
if (name.equals("考官")) {
milliSleep(1000);
System.out.printf("%s 分发试卷!\n", name);
phaser.arriveAndAwaitAdvance();
} else {
milliSleep(1000);
System.out.printf("%s 等待考试!\n", name);
phaser.arriveAndAwaitAdvance();
}
milliSleep(1000);
System.out.printf("%s 考试中!\n", name);
phaser.arriveAndAwaitAdvance();
milliSleep(1000);
System.out.printf("%s 考试结束!\n", name);
phaser.arriveAndAwaitAdvance();
if (name.equals("考官")) {
milliSleep(1000);
System.out.printf("%s 收试卷!\n", name);
phaser.arriveAndAwaitAdvance();
} else {
milliSleep(1000);
System.out.printf("%s 离开考场!\n", name);
phaser.arriveAndDeregister();
}
milliSleep(1000);
System.out.printf("%s 测试register!\n", name);
phaser.register();
}
}
}
ReadWriteLock
ReadWriteLock
即读写锁,其中读锁是共享锁,写锁是排他锁。在一个读多写少的场景下,如果使用普通的锁,那么效率会非常低。我们希望读线程加锁时,其他读线程也可以读。写线程在写的时候读线程和其他写线程需要等待。举个例子:
public class TestReadWriteLock {
private static int value;
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
public static void read(Lock lock) {
try {
lock.lock();
Thread.sleep(1000);
System.out.println("read over! v = " + value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void write(Lock lock, int v) {
try {
lock.lock();
Thread.sleep(1000);
value = v;
System.out.println("write over!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Runnable readR = () -> read(readLock);
Runnable writeR = () -> write(writeLock, new Random().nextInt());
for (int i = 0; i < 5; i++) {
new Thread(writeR).start();
}
for (int i = 0; i < 18; i++) {
new Thread(readR).start();
}
}
}
可以看到read(Lock lock)
方法里有sleep
方法进行阻塞,但是由于读锁是共享锁,所以看输出好像read
方法是同时运行的,并没有每个线程相隔1秒依次运行。而write
方法中的锁是排他锁,所以 write
方法运行时其他线程都在等待。
Semaphore
Semaphore
译为信号灯,个人觉得就是来控制同时可以运行几个线程的。例如现在Semaphore s = new Semaphore(1)
,来个线程t1acquire
一下,那就由1变成0,并且当前线程可以继续执行后续任务。另一个线程t2调用acquire
时,如果还是0,则阻塞在这里。直到第一个线程t1通过release
后又把0变回1,t2才会将1变成0,并且继续运行下去。同时Semaphore
也是可以设置为公平锁和非公平锁的。例子如下:
public class TestSemaphore {
public static void main(String[] args) {
Semaphore s = new Semaphore(1, true);
new Thread(() -> {
try {
s.acquire();
System.out.println("T1 running...");
Thread.sleep(200);
System.out.println("T1 running...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
new Thread(() -> {
try {
s.acquire();
System.out.println("T2 running...");
Thread.sleep(200);
System.out.println("T2 running...");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
Exchanger
Exchanger
比较好理解,就是两个线程交换数据用的。通过exchanger.exchange()
方法阻塞住线程来交换数据。例子如下:
public class TestExchanger {
static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) {
new Thread(()->{
String s = "T1";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t1").start();
new Thread(()->{
String s = "T2";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t2").start();
}
}
LockSupport
LockSupport
是一个比较底层的工具类,是用来创建锁和其他同步工具类的基本线程阻塞原语,是Java
锁和同步器框架的核心。AQS: AbstractQueuedSynchronizer
,就是通过调用LockSupport.park()
和LockSupport.unpark()
的方法,来实现线程的阻塞和唤醒的。
public class TestLockSupport {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
LockSupport.park();
System.out.println("first park");
}
if (i == 8) {
LockSupport.park();
System.out.println("second park");
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
LockSupport.unpark(t);
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after 8 seconds!");
LockSupport.unpark(t);
}
}
LockSupport.park()
可以让线程阻塞,LockSupport.unpark(t)
可以唤醒线程。且LockSupport.unpark(t)
可以先于LockSupport.park()
执行,并且线程不会阻塞。
不同锁的应用
现在尝试用不同的锁来实现这样一个需求:实现一个容器,提供两个方法add
和size
。并且写两个线程,满足线程1添加10个元素到容器中,线程2监控元素个数,当元素个数到达5时线程2给出提示并结束。
使用synchronized
public class TestSynchronized {
volatile List<Object> lists = new ArrayList<>();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
final Object lock = new Object();
TestSynchronized c = new TestSynchronized();
new Thread(() -> {
synchronized(lock) {
System.out.println("t2启动");
if(c.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2结束");
lock.notify();
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1启动");
synchronized(lock) {
for(int i=0; i<10; i++) {
c.add(new Object());
System.out.println("add " + i);
if(c.size() == 5) {
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1").start();
}
}
使用CountDownLatch
public class TestCountDownLatch {
volatile List<Object> lists = new ArrayList<>();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
TestCountDownLatch c = new TestCountDownLatch();
CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
new Thread(() -> {
System.out.println("t2启动");
if (c.size() != 5) {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
latch2.countDown();
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
latch.countDown();
try {
latch2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}
使用LockSupport
public class TestLockSupport {
volatile List<Object> lists = new ArrayList<>();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
TestLockSupport c = new TestLockSupport();
t1 = new Thread(() -> {
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
LockSupport.unpark(t2);
LockSupport.park();
}
}
}, "t1");
t2 = new Thread(() -> {
LockSupport.park();
System.out.println("t2 结束");
LockSupport.unpark(t1);
}, "t2");
t1.start();
t2.start();
}
}