一:java并发包
jdk 1.5引入了java.util.concurrent并发包,里面提供了各种强大的并发工具,让并发编程变得更容易。本文简单介绍一下并发包提供的锁(lock)。
二:锁的使用
使用并发包的锁时,通常代码结构如下:
Lock l = ...;
l.lock();
try {
// 操作共享资源
} finally {
l.unlock();
}
(1)ReentrantLock。ReentrantLock作用就像synchronized关键字。
下面是使用ReentrantLock的示例
public class ReentrantLockTest {
private static class NamePrinter {
private Lock lock = new ReentrantLock();
public void print(String name) {
try {
lock.lock();
//将名字逐个字母输出
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
System.out.println();
} finally {
//确保最后能释放锁
lock.unlock();
}
}
}
public static void main(String[] args) {
final NamePrinter namePrinter = new NamePrinter();
while (true) {
new Thread(new Runnable() {
@Override
public void run() {
namePrinter.print("liudehua");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
namePrinter.print("zhangxueyou");
}
}).start();
}
}
}
(2)ReentrantReadWriteLock。读写锁,多个线程之间可以同时读数据,但如果一个线程在写数据,其他线程不能写数据也不能读数据。
ReentrantReadWriteLock 示例1:模拟一个队列,多个线程之间可以同时读数据,但如果一个线程在写数据,其他线程不能写数据也不能读数据
public class MockQueueTest {
public static void main(String[] args) {
final MockQueue queue = new MockQueue();
while (true) {
new Thread(new Runnable() {
@Override
public void run() {
queue.write();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
queue.read();
}
}).start();
}
}
/**
* 模拟一个队列。
*/
private static class MockQueue {
private int data = -1;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock rl = readWriteLock.readLock();
private final Lock wl = readWriteLock.writeLock();
public void read() {
rl.lock();
try {
System.out.println(Thread.currentThread().getName() + " is ready to read data...");
TimeUnit.SECONDS.sleep((long) (Math.random() * 3));
System.out.println(Thread.currentThread().getName() + " reads the data:" + data);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rl.unlock();
}
}
public void write() {
wl.lock();
try {
System.out.println(Thread.currentThread().getName() + " is ready to write data...");
int temp = (int) (Math.random() * 10000);
this.data = temp;
TimeUnit.SECONDS.sleep((long) (Math.random() * 3));
System.out.println(Thread.currentThread().getName() + " writes the data:" + temp);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
wl.unlock();
}
}
}
}
ReentrantReadWriteLock 示例2:模拟一个简单的缓存
public class SimpleCacheTest {
public static void main(String[] args) {
final SimpleCache simpleCache = new SimpleCache();
new Thread(new Runnable() {
@Override
public void run() {
Integer value1 = simpleCache.get("key1");
System.out.println("value1=" + value1);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Integer value2 = simpleCache.get("key1");
System.out.println("value2=" + value2);
}
}).start();
}
private static class SimpleCache {
private Map<String, Integer> cacheMap = new HashMap<String, Integer>();
private final Random random = new Random();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock rl = readWriteLock.readLock();
private final Lock wl = readWriteLock.writeLock();
public Integer get(String key) {
rl.lock();
Integer value = null;
try {
value = cacheMap.get(key);
if (value == null) {
rl.unlock();
//A
wl.lock();
try {
value = cacheMap.get(key);
if (value == null) { //这里需要再次判断,因为在A处可能有多个线程在等待
value = random.nextInt(1000);
TimeUnit.SECONDS.sleep(random.nextInt(3));
System.out.println("put data into cache...");
cacheMap.put(key, value);
} else {
System.out.print("get data from cache....");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
wl.unlock();
}
rl.lock();//释放写锁后再加上读锁
} else {
System.out.print("get data from cache....");
}
} finally {
rl.unlock();
}
return value;
}
}
}
三:线程通信
生成lock对象后,可以调用newCondition方法获得Condition对象,从而调用Condition对象的await、signal/signalAll方法来实现线程通信。await的作用就像wait方法,而signal/signalAll的作用就像notify/notifyAll方法。注意:一个lock对象可以绑定多个Condition对象。
示例1:计算1到100的和。一个线程负责计算,另一个线程负责获取计算结果。
public class CalculatorTest {
private static class Calculator {
private boolean shouldCalculate = true;//是否是计算
private int result;//计算结果
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
/**
* 计算
*/
public void calculate() {
lock.lock();
while (!shouldCalculate) { //如果不是计算,则等待
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
for (int i = 1; i <= 100; i++) {
result += i;
}
shouldCalculate = false;
condition.signal();
} finally {
lock.unlock();
}
}
/**
* 返回计算结果
*/
public int getResult() {
lock.lock();
try {
while (shouldCalculate) { //如果是计算,则等待
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
shouldCalculate = true;
condition.signal();
return result;
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final Calculator calculator = new Calculator();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("the result is:" + calculator.getResult());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
calculator.calculate();
}
}).start();
}
}
示例2:实现一个阻塞队列 。如果队列满了则不能放入元素,直到有空间;空了不能取元素,直到有元素入队。注:下面代码来自jdk示例
public class ArrayBlockingQueue {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items = new Object[100];
private 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();
}
}
}