笔记摘要
这里介绍了java5中的线程锁技术:Lock和Condition,实现线程间的通信,其中的读锁和写锁的使用通过一个缓存系统进行了演示,对于Condition的应用通过一个阻塞队列进行演示。
线程锁技术:Lock & Condition 实现线程同步通信所属包:java.util.concurrent.locks
线程锁 | 说明 | Synchronized同步方法,锁对象是this;同步静态方法,锁对象是字节码.class;同步代码块,锁对象是任意对象,但必须是同一个对象Lock同步锁接口ReentrantLocklock(),unlock(),newCondition()ReadWriteLock读写锁接口ReentrantReadWriteLockreadLock()获取读锁,writeLock()获取写锁Condition线程间通信 await()等待 signal()唤醒1. Lock
Lock比传统线程模型中的synchronized方式更加面向对象,相对于synchronized 方法和语句它具有更广泛的锁定操作,此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
于现实生活中类似,锁本身也是一个对象。两个线程执行的代码片段要实现同步互斥的结果,它们必须用同一个Lock对象,锁是上在代表要操作的资源的类的内部方法中,而不是线程代码中。
ReentrantLock
方法声明 | 功能描述 | lock()获取锁tryLock()尝试获取锁unock()释放锁newCondition()获取锁的Condition常用形式如下
Lock lock = new ReentrantLock();public void doSth(){ lock.lock(); try { }finally { lock.unlock(); }}
读写锁
分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由JVM自己控制的。你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
读写锁的使用情景:
- 如果代码只读数据,就可以很多人共同读取,但不能同时写。
- 如果代码修改数据,只能有一个人在写,且不能同时读数据。
API中ReentrantReadWriteLock类提供的一个读写锁缓存示例:
class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { rwl.readLock().unlock(); rwl.writeLock().lock(); if (!cacheValid) { data = ... cacheValid = true; } rwl.readLock().lock(); rwl.writeLock().unlock(); } use(data); rwl.readLock().unlock(); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
读写锁的应用:编写一个缓存系统
注解:为了避免线程的安全问题,synchronized和ReadWriteLock都可以,synchronized也防止了并发读取,性能较低有一个线程先进去,开始读取数据,进行判断,发现没有数据,其他线程就没有必要进去了,就释放读锁,加上写锁,去查找数据写入,为了避免写入的其他对象等待,再做一次判断,数据写入完成后,释放写锁,上读锁,防止写入,还原原来的状态。
两次判断:第一次为了写入数据,所以释放读锁,上写锁。第二次为了防止阻塞的线程重复写入
import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class CacheDemo { private Map<String, Object> cache = new HashMap<String, Object>(); private ReadWriteLock rwl = new ReentrantReadWriteLock(); public Object getData(String key){ rwl.readLock().lock(); Object value = null; try{ value = cache.get(key); if(value == null){ rwl.readLock().unlock(); rwl.writeLock().lock(); try{ if(value==null){ value = "aaaa"; } }finally{ rwl.writeLock().unlock(); } rwl.readLock().lock(); } }finally{ rwl.readLock().unlock(); } return value; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
虚假唤醒:用while代替if
Lock lock = new ReentrantLock();try { lock.lock(); }finally { lock.unlock();}
读写锁测试
public class ReadWriteLockTest { public static void main(String[] args) { final Queue3 q3 = new Queue3(); for(int i=0;i<3;i++) { new Thread(){ public void run(){ while(true){ q3.get(); } } }.start(); new Thread(){ public void run(){ while(true){ q3.put(new Random().nextInt(10000)); } } }.start(); } }}class Queue3{ private Object data = null;ReadWriteLock rwl = new ReentrantReadWriteLock(); public void get(){ rwl.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + " be ready to read data!"); Thread.sleep((long)(Math.random()*1000)); System.out.println(Thread.currentThread().getName() + "have read data :" + data); } catch (InterruptedException e) { e.printStackTrace(); }finally{ rwl.readLock().unlock(); } } public void put(Object data){ rwl.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + " be ready to write data!"); Thread.sleep((long)(Math.random()*1000)); this.data = data; System.out.println(Thread.currentThread().getName() + " have write data: " + data); } catch (InterruptedException e) { e.printStackTrace(); }finally{ rwl.writeLock().unlock(); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
Thread-0 be ready to read data!Thread-2 be ready to read data!Thread-4 be ready to read data!Thread-0have read data :nullThread-2have read data :nullThread-4have read data :nullThread-5 be ready to write data!Thread-5 have write data: 7975Thread-5 be ready to write data!Thread-5 have write data: 9832Thread-3 be ready to write data!Thread-3 have write data: 2813Thread-3 be ready to write data!Thread-3 have write data: 7998Thread-1 be ready to write data!Thread-1 have write data: 6737Thread-1 be ready to write data!...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
2. Condition
用于实现线程间的通信,是为了解决Object.wait()、nitify()、notifyAll()难以使用的问题
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法wait和notify的使用
一个锁内部可以有多个Condition,即有多路等待通知,传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。使用一个监视器往往会产生顾此失彼的情况。
在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
方法声明 | 功能描述 | await()线程等待await(long time, TimeUnit unit)线程等待特定的时间,超过等待时间则为超时signal()随机唤醒某个等待线程signalAll()唤醒所有等待中的线程Condition的应用:阻塞队列(使用了两个监视器)
说明:该应用是 java.util.concurrent.locks包中Condition接口中的示例代码。使用了两个Condition分别用于管理取数据的线程,和存数据的线程,这样就可以明确的唤醒需要的一类线程,如果使用一个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(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
Condition测试
public class ConditionCommunication { public static void main(String[] args) { final Business business = new Business(); new Thread( new Runnable() { @Override public void run() { for(int i=1;i<=5;i++){ business.sub(i); } } } ).start(); for(int i=1;i<=5;i++){ business.main(i); } } class Business { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); private boolean bShouldSub = true; public void sub(int i){ lock.lock(); try{ while(!bShouldSub){ try { condition.await(); } catch (Exception e) { e.printStackTrace(); } } for(int j=1;j<=2;j++){ System.out.println("sub thread sequence of " + j + ",loop of " + i); } bShouldSub = false; condition.signal(); }finally{ lock.unlock(); } } public void main(int i){ lock.lock(); try{ while(bShouldSub){ try { condition.await(); } catch (Exception e) { e.printStackTrace(); } } for(int j=1;j<=4;j++){ System.out.println("main thread sequence of " + j + ",loop of " + i); } bShouldSub = true; condition.signal(); }finally{ lock.unlock(); } } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
输出结果
sub thread sequence of 1,loop of 1sub thread sequence of 2,loop of 1main thread sequence of 1,loop of 1main thread sequence of 2,loop of 1main thread sequence of 3,loop of 1main thread sequence of 4,loop of 1sub thread sequence of 1,loop of 2sub thread sequence of 2,loop of 2main thread sequence of 1,loop of 2main thread sequence of 2,loop of 2main thread sequence of 3,loop of 2main thread sequence of 4,loop of 2sub thread sequence of 1,loop of 3sub thread sequence of 2,loop of 3main thread sequence of 1,loop of 3main thread sequence of 2,loop of 3main thread sequence of 3,loop of 3main thread sequence of 4,loop of 3sub thread sequence of 1,loop of 4sub thread sequence of 2,loop of 4main thread sequence of 1,loop of 4main thread sequence of 2,loop of 4main thread sequence of 3,loop of 4main thread sequence of 4,loop of 4sub thread sequence of 1,loop of 5sub thread sequence of 2,loop of 5main thread sequence of 1,loop of 5main thread sequence of 2,loop of 5main thread sequence of 3,loop of 5main thread sequence of 4,loop of 5
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
使用ReentrantLock和Condition实现一个简单的阻塞队列MyArrayBlockingQueue,如果调用take方法时集合中没有数据,那么调用线程就阻塞;如果调用put方法时,集合数据已满,那么也会引起调用线程阻塞。但是,这两个阻塞的条件时不同的,分别为为notFull和notEmpty
import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class MyArrayBlockingQueue<T> { private final T[] items; private final Lock lock = new ReentrantLock(); private Condition notFull = lock.newCondition(); private Condition notEmpty = lock.newCondition(); private int head; private int tail; private int count; public MyArrayBlockingQueue(int maxSize) { items = (T[]) new Object[maxSize]; } public MyArrayBlockingQueue() { this(10); } public void put(T t) { lock.lock(); try { while (count == getCapacity()) { System.out.println("数据已满,等待"); notFull.await(); } items[tail] = t; if (++tail == getCapacity()) { tail = 0; } ++count; notEmpty.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public T take() { lock.lock(); try { while (count == 0) { System.out.println("还没有数据,请等待"); notEmpty.await(); } T ret = items[head]; items[head] = null; if (++head == getCapacity()) { head = 0; } --count; notFull.signalAll(); return ret; } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return null; } public int getCapacity() { return items.length; } public int size() { lock.lock(); try { return count; } finally { lock.unlock(); } } public static void main(String[] args) { MyArrayBlockingQueue<Integer> aQueue = new MyArrayBlockingQueue<Integer>(); aQueue.put(3); aQueue.put(24); for (int i = 0; i < 5; i++) { System.out.println(aQueue.take()); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
输出结果
324还没有数据,请等待
3. Condition练习
一共有3个线程,两个子线程先后循环2次,接着主线程循环3次,接着又回到两 个子线程先后循环2次,再回到主线程又循环3次,如此循环5次。
思路:老二先执行,执行完唤醒老三,老三执行完唤醒老大,老大执行完唤醒老二,以此循环,所以定义3个Condition对象和一个执行标识即可
示例出现的问题:两个文件中有同名类的情况
解决方案:可以将一个文件中的那个同名外部类放进类中,但是静态不能创建内部类的实例对象,所以需要加上static,这样两个类的名称就不一样了。 一个是原来的类名,一个是在自己类名前面加上外部类的类名。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreeConditionCommunication { public static void main(String[] args){ final Business business = new Business(); new Thread(new Runnable(){ @Override public void run() { for(int i=1;i<=5;i++){ business.sub2(i); } } }).start(); new Thread(new Runnable(){ @Override public void run() { for(int i=1;i<=5;i++){ business.sub3(i); } } }).start(); for(int i=1;i<=5;i++){ business.main(i); } } static class Business{ Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); private int ShouldSub = 1; public void sub2(int i){ lock.lock(); try{ if(ShouldSub != 2){ try { condition2.await(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int j=1;j<=2;j++){ System.out.println("sub thread sequence of"+i+",loop of "+j); } ShouldSub = 3; condition3.signal(); }finally{ lock.unlock(); } } public void sub3(int i){ lock.lock(); try{ if(ShouldSub != 3){ try { condition3.await(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int j=1;j<=2;j++){ System.out.println("sub2 thread sequence of"+i+",loop of "+j); } ShouldSub = 1; condition1.signal(); }finally{ lock.unlock(); } } public void main(int i){ lock.lock(); try{ if(ShouldSub!=1){ try { condition1.await(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int j=1;j<=3;j++){ System.out.println("main thread sequence of"+i+", loop of "+j); } ShouldSub = 2; condition2.signal(); }finally{ lock.unlock(); } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
输出结果
main thread sequence of1, loop of 1main thread sequence of1, loop of 2main thread sequence of1, loop of 3sub thread sequence of1,loop of 1sub thread sequence of1,loop of 2sub2 thread sequence of1,loop of 1sub2 thread sequence of1,loop of 2main thread sequence of2, loop of 1main thread sequence of2, loop of 2main thread sequence of2, loop of 3sub thread sequence of2,loop of 1sub thread sequence of2,loop of 2sub2 thread sequence of2,loop of 1sub2 thread sequence of2,loop of 2main thread sequence of3, loop of 1main thread sequence of3, loop of 2main thread sequence of3, loop of 3sub thread sequence of3,loop of 1sub thread sequence of3,loop of 2sub2 thread sequence of3,loop of 1sub2 thread sequence of3,loop of 2main thread sequence of4, loop of 1main thread sequence of4, loop of 2main thread sequence of4, loop of 3sub thread sequence of4,loop of 1sub thread sequence of4,loop of 2sub2 thread sequence of4,loop of 1sub2 thread sequence of4,loop of 2main thread sequence of5, loop of 1main thread sequence of5, loop of 2main thread sequence of5, loop of 3sub thread sequence of5,loop of 1sub thread sequence of5,loop of 2sub2 thread sequence of5,loop of 1sub2 thread sequence of5,loop of 2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
4. 多路等待和通知
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(); } }}