从 JDK 1.5 开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大的简化多线程程序的编写。
java.util.concurrent.locks包提供的ReentrantLock类,一个可重入的互斥锁,它具有与使用synchronized加锁一样的特性,并且功能更加强大。
(目录)
Counter接口
package com.example.demo.thread;
public interface Counter {
void add();
int getCount();
}
不加锁的 Counter实现
package com.example.demo.thread.impl;
import com.example.demo.thread.Counter;
/**
* 不加锁实现
*/
public class NonLockCounter implements Counter {
private int count;
public void add() {
// 延迟操作
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
public int getCount() {
return this.count;
}
}
测试类
package com.example.demo.thread;
import com.example.demo.thread.impl.SynchronizedCounter;
import org.junit.Test;
public class ThreadTests {
@Test
public void testCounter() throws InterruptedException {
Counter counter = new NonLockCounter();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
counter.add();
}
}).start();
}
Thread.sleep(1000);
System.out.println(counter.getCount());
}
}
多次测试结果
97
98
100
99
97
synchronized 加锁实现
package com.example.demo.thread.impl;
import com.example.demo.thread.Counter;
/**
* synchronized 加锁实现
*/
public class SynchronizedCounter implements Counter {
private int count;
public synchronized void add() {
// 延迟操作
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
public int getCount() {
return this.count;
}
}
测试类
package com.example.demo.thread;
import com.example.demo.thread.impl.NonLockCounter;
import com.example.demo.thread.impl.SynchronizedCounter;
import org.junit.Test;
public class ThreadTests {
@Test
public void testSynchronizedCounter() throws InterruptedException {
Counter counter = new SynchronizedCounter();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
counter.add();
}
}).start();
}
Thread.sleep(1000);
System.out.println(counter.getCount());
}
}
多次测试结果
100
100
100
100
100
ReentrantLock 加锁实现
package com.example.demo.thread.impl;
import com.example.demo.thread.Counter;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock 加锁实现
*/
public class ReentrantLockCounter implements Counter {
private int count;
private final Lock lock = new ReentrantLock();
public void add() {
// 加锁
lock.lock();
try {
// 延迟操作
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
} finally {
// 释放锁
lock.unlock();
}
}
public int getCount() {
return this.count;
}
}
测试类
package com.example.demo.thread;
import com.example.demo.thread.impl.NonLockCounter;
import com.example.demo.thread.impl.ReentrantLockCounter;
import com.example.demo.thread.impl.SynchronizedCounter;
import org.junit.Test;
public class ThreadTests {
@Test
public void testReentrantLockCounter() throws InterruptedException {
Counter counter = new ReentrantLockCounter();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
counter.add();
}
}).start();
}
Thread.sleep(1000);
System.out.println(counter.getCount());
}
}
多次测试结果
100
100
100
100
100
总结
我们可以总结出synchronized和ReentrantLock有以下几点不一样。
ReentrantLock需要手动调用加锁方法; synchronized不需要,它采用了隐藏的加锁方式,借助 jvm 来实现;
synchronized不需要考虑异常; 而ReentrantLock获取锁之后,要在finally中正确的释放锁,否则会影响其它线程
ReentrantLock拥有尝试获取锁的超时机制,利用它可以避免无限等待; 而synchronized不具备
synchronized是 Java 语言层面提供的语法; 而ReentrantLock是 Java 代码实现的可重入锁
因此,在并发编程中,使用ReentrantLock比直接使用synchronized更灵活、更安全,采用tryLock
方法,即使未获取到锁也不会导致死锁。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
Condition
Condition提供的await()、signal()、signalAll()原理和synchronized锁对象的wait()、notify()、notifyAll()是一致的,并且其行为也是一样的。
简单的示例
package com.example.demo.thread.impl;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionCounter {
private final Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await() {
// 加锁
lock.tryLock();
try {
System.out.println("await开始等待");
condition.await();
System.out.println("await结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public void signal() {
// 加锁
lock.tryLock();
try {
// 延迟操作
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒某个等待线程
condition.signal();
System.out.println("signal 唤醒通知完毕");
} finally {
// 释放锁
lock.unlock();
}
}
}
package com.example.demo.thread;
import com.example.demo.thread.impl.ConditionCounter;
import org.junit.Test;
public class ThreadTests {
@Test
public void testConditionCounter() throws InterruptedException {
ConditionCounter counter= new ConditionCounter();
// 先启动执行等待的线
new Thread(new Runnable() {
@Override
public void run() {
counter.await();
}
}).start();
Thread.sleep(3000);
// 过3秒,再启动执行通知的线程
new Thread(new Runnable() {
@Override
public void run() {
counter.signal();
}
}).start();
}
}
运行结果
await开始等待
signal 唤醒通知完毕
await结束等待
相比wait/notify/notifyAll的等待/通知模型,Condition更加灵活,理由有以下几点:
notify()方法唤醒线程时,被通知的线程由 Java 虚拟机随机选择; 而采用ReentrantLock结合Condition可以实现有选择性地通知,这一特性在实际编程中非常实用 一个Lock里面可以创建多个Condition实例,实现多路通知,使用多个Condition的应用场景很常见,比如ArrayBlockingQueue