随着学习的深入,我接触了更多之前没有接触到的知识,对线程间的同步通信有了更多的认识,之前已经学习过synchronized 实现线程间同步通信,今天来学习更多的--Lock,GO!!!
一、初时Lock
Lock比传统线程模型中的synchronized更加面向对象,与生活中的锁类似,锁本身也应该是一个对象,两个线程执行的代码块要实现同步互斥的效果,他们必须用同一个lock对象,锁是上在代表要操作的资源类的背部方法中,而不是线程代码中。看一下具体的代码,如何使用Lock对象:
1 public class LockTest {
2
3 public static void main(String[] args) {
4 new LockTest().init();
5 }
6
7 private void init() {
8 outputer outputer = new outputer();
9 new Thread(new Runnable() {
10 @Override
11 public void run() {
12 while (true) {
13 try {
14 Thread.sleep(10);
15 } catch (InterruptedException e) {
16 e.printStackTrace();
17 }
18 outputer.output("songshengchao");
19 }
20 }
21 }).start();
22
23 new Thread(new Runnable() {
24 @Override
25 public void run() {
26 while (true) {
27 try {
28 Thread.sleep(10);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 outputer.output("dongna");
33 }
34 }
35 }).start();
36 }
37
38 static class outputer {
39 // 创建锁对象
40 Lock lock = new ReentrantLock();
41
42 public void output(String name) {
43 int len = name.length();
44 // 加上锁
45 lock.lock();
46 try {
47 for (int i = 0; i < len; i++) {
48 System.out.print(name.charAt(i));
49 }
50 System.out.println();
51 }finally {
52 // 释放锁
53 lock.unlock();
54 }
55
56 }
57 }
二、读写锁
读写锁,分为读锁和写锁,多个读锁不互斥,读锁和写锁互斥,写锁与写锁互斥,这是由JVM自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但是不能同时写,那就上读锁。如果你的代码在修改数据,只能有一个人在写,并且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁。
看看代码中如何实现:
1 import java.util.Random;
2 import java.util.concurrent.locks.ReadWriteLock;
3 import java.util.concurrent.locks.ReentrantReadWriteLock;
4
5 public class ReadWriteLockTest {
6
7 public static void main(String[] args) {
8
9 final Queue3 q3 = new Queue3();
10 for (int i = 0; i < 3; i++) {
11 new Thread(new Runnable() {
12 @Override
13 public void run() {
14 while (true) {
15 q3.get();
16 }
17 }
18 }).start();
19
20 new Thread(new Runnable() {
21 @Override
22 public void run() {
23 while (true) {
24 q3.put(new Random().nextInt(10000));
25 }
26
27 }
28 }).start();
29 }
30 }
31
32 static class Queue3 {
33
34 // 共享数据 只有一个线程可以写数据 多个线程读数据
35 private Object data = null;
36 // 读写锁对象
37 ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
38
39 public void get() {
40 // 读锁 上锁
41 readWriteLock.readLock().lock();
42 try {
43 System.out.println(Thread.currentThread().getName() + " be ready to read ");
44 Thread.sleep((long) (Math.random() * 1000));
45 System.out.println(Thread.currentThread().getName() + " have read data " + data);
46 } catch (InterruptedException e) {
47 e.printStackTrace();
48 }finally {
49 // 读锁 开锁
50 readWriteLock.readLock().unlock();
51 }
52
53 }
54
55 public void put(Object data) {
56
57 // 写锁 上锁
58 readWriteLock.writeLock().lock();
59 try {
60 System.out.println(Thread.currentThread().getName() + " be ready to write ");
61 Thread.sleep((long) (Math.random() * 1000));
62 this.data = data;
63 System.out.println(Thread.currentThread().getName() + " have write data " + data);
64 } catch (InterruptedException e) {
65 e.printStackTrace();
66 } finally {
67 // 写锁 开锁
68 readWriteLock.writeLock().unlock();
69 }
70 }
71 }
72 }
三、缓存系统的伪代码设计
这个jdk API文档中有一个很好的例子,就是在ReentrantReadWriteLock类中,具体可以自己看一下~
主要是读写锁的实际应用,你一定要思路清晰,具体代码如何执行,都要搞清楚,注释也比较详细,哈哈!代码如下:
1 import java.util.HashMap;
2 import java.util.Map;
3 import java.util.concurrent.locks.ReadWriteLock;
4 import java.util.concurrent.locks.ReentrantReadWriteLock;
5
6 public class CacheDemo {
7
8 private Map<String, Object> cache = new HashMap<String, Object>();
9 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
10
11 public static void main(String[] args) {
12
13 }
14
15 // 这个是获取缓存中数据的方法
16 public Object getData(String key) {
17 readWriteLock.readLock().lock();
18 Object value = null;
19 try {
20 value = cache.get(key);
21 // 如果多个线程同时执行数据库的查询 那就要执行多次数据库查询,浪费内存 可以加synchronized 上锁,但是用读写锁是更好的方法
22 if (value == null) {
23 // 这里代码就是从数据库中获取实际的相关数据,但是如果多线程的情况下呢?代码执行到这里,需要将从数据库中读取到的数据,写入到内存中
24 // 释放读锁
25 readWriteLock.readLock().unlock();
26 // 加上写锁,只能有一个线程进行写数据的操作
27 readWriteLock.writeLock().lock();
28 try {
29 // 预防多线程中同时进行写操作的线程进行数据的获取
30 if(value == null) {
31 value = "queryDB.getData()";
32 }
33 } catch (Exception e) {
34 e.printStackTrace();
35 } finally {
36 // 数据写进内存之后 释放写锁
37 readWriteLock.writeLock().unlock();
38 }
39 readWriteLock.readLock().lock();
40 }
41 } catch (Exception e) {
42 e.printStackTrace();
43 } finally {
44 readWriteLock.readLock().unlock();
45 }
46 return value;
47 }
48
49 }
四、Condition来实现线程间的通讯
Condition的功能类似在传统线程技术中的Object的wait()和notify()的功能。在等待Condition的时候,允许“虚假唤醒”,这通常作为基础平台语义的让步。对大多数应用程序来说,这带来的实际影响很小,因为Condition总是在一个循环中被等待。并测试正在等待的状态说明
注意:Condition是跟随Lock对象的
JDK 中 Condition类中的例子,经典中的经典:(JDK中的解释说明)
Condition
实例实质上被绑定到一个锁上。要为特定 Lock
实例获得 Condition
实例,请使用其 newCondition()
方法。
作为一个示例,假定有一个绑定的缓冲区,它支持 put
和 take
方法。如果试图在空的缓冲区上执行 take
操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put
操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put
线程和 take
线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition
代码如下:(这段代码其实挺不好理解的,为什么用两个Condition?)
1 class BoundedBuffer {
2 final Lock lock = new ReentrantLock();
3 final Condition notFull = lock.newCondition();
4 final Condition notEmpty = lock.newCondition();
5
6 final Object[] items = new Object[100];
7 int putptr, takeptr, count;
8
9 public void put(Object x) throws InterruptedException {
10 lock.lock();
11 try {
12 while (count == items.length)
13 notFull.await();
14 items[putptr] = x;
15 if (++putptr == items.length) putptr = 0;
16 ++count;
17 notEmpty.signal();
18 } finally {
19 lock.unlock();
20 }
21 }
22
23 public Object take() throws InterruptedException {
24 lock.lock();
25 try {
26 while (count == 0)
27 notEmpty.await();
28 Object x = items[takeptr];
29 if (++takeptr == items.length) takeptr = 0;
30 --count;
31 notFull.signal();
32 return x;
33 } finally {
34 lock.unlock();
35 }
36 }
37 }
终极难题,实现两个以上线程同时交替运行的代码,condition类来实现,之前写那个两个线程交替运行的时候,试着写了一下,大于2个线程如何写,但是没有写出来,今天终于解决了那个问题,代码如下:
1 public class ThreeConditionCommunication {
2
3 public static void main(String[] args) {
4 Business3 business = new Business3();
5 // 线程2
6 new Thread(new Runnable() {
7
8 @Override
9 public void run() {
10 for (int i = 1; i <= 50; i++) {
11 business.sub2(i);
12 }
13 }
14 }).start();
15
16 // 线程3
17 new Thread(new Runnable() {
18
19 @Override
20 public void run() {
21 for (int i = 1; i <= 50; i++) {
22 business.sub3(i);
23 }
24 }
25 }).start();
26
27 // 本身main方法就是主线程,线程1 直接可以写循环代码
28 for (int i = 1; i <= 50; i++) {
29 business.main(i);
30 }
31
32 }
33
34 }
35 ---------------------------------------上面是测试代码----------------------------------------
36
37 /**
38 * 改造 用Condition实现三个线程间的通讯
39 *
40 * @author ssc
41 *
42 */
43 public class Business3 {
44
45 // 是否是子线程执行 默认子线程先执行 默认主线程先执行
46 private int shouldSub = 1;
47 private Lock lock = new ReentrantLock();
48 private Condition condition1 = lock.newCondition();
49 private Condition condition2 = lock.newCondition();
50 private Condition condition3 = lock.newCondition();
51
52 public void sub2(int i) {
53 lock.lock();
54 try {
55 // 不是线程2应该执行 让给线程3 线程2执行等待的方法
56 while (shouldSub != 2) {
57 // this.wait();
58 // Condition类特有的等待方法 await() 线程2等待
59 condition2.await();
60 }
61 for (int j = 1; j <= 10; j++) {
62 System.out.println("sub2 thread sequece of" + j + ", loop of " + i);
63 }
64 // 线程2执行完毕后 让给线程3执行
65 shouldSub = 3;
66 // 唤醒线程3
67 // this.notify();
68 // 等同于 notify()
69 condition3.signal();
70 } catch (Exception e) {
71 e.printStackTrace();
72 } finally {
73 lock.unlock();
74 }
75 }
76
77 public void sub3(int i) {
78 lock.lock();
79 try {
80 // 不是线程3应该执行 让给线程3 线程2执行等待的方法
81 while (shouldSub != 3) {
82 // this.wait();
83 // Condition类特有的等待方法 await() 线程3等待
84 condition3.await();
85 }
86 for (int j = 1; j <= 20; j++) {
87 System.out.println("sub3 thread sequece of" + j + ", loop of " + i);
88 }
89 // 线程3执行完毕后 让给线程1 也就是主线程执行
90 shouldSub = 1;
91 // 唤醒线程1
92 // this.notify();
93 // 等同于 notify()
94 condition1.signal();
95 } catch (Exception e) {
96 e.printStackTrace();
97 } finally {
98 lock.unlock();
99 }
100 }
101
102 public void main(int i) {
103 lock.lock();
104 try {
105 // 是线程2应该执行 让给线程2执行 主线程执行等待的方法
106 while (shouldSub != 1) {
107 //this.wait();
108 condition1.await();
109 }
110 for (int j = 1; j <= 100; j++) {
111 System.out.println("main thread sequece of" + j + ", loop of " + i);
112 }
113 // 主线程执行费完毕后 交给子线程执行
114 shouldSub = 2;
115 // 唤醒线程2
116 //this.notify();
117 condition2.signal();
118 } catch (Exception e) {
119 e.printStackTrace();
120 } finally {
121 lock.unlock();
122 }
123 }
124 }