线程高级应用-心得5-java5线程并发库中Lock和Condition实现线程同步通讯
1.Lock相关知识介绍
好比我同时种了几块地的麦子,然后就等待收割。收割时,则是哪块先熟了,先收割哪块。
下面举一个面试题的例子来引出Lock缓存读写锁的案例,一个load()和get()方法返回值为空时的情况;load()的返回值是一个代理对象,而get()却是一个实实在在的对象;所以当返回对象为空是,get()返回null,load()返回一个异常对象;具体分析如下:
一个读写锁的缓存库案例;用上面那道面试题分析则很好理解:
线程阻塞问题:运用多个Condition对象解决
2. Lock接口锁的使用
Lock与synchronized最大区别就是:前者更面向对象;Lock要求程序员手动释放锁,synchronized自动释放
1 package com.java5.thread.newSkill; 2 3 //concurrent就是java5新增的线程并发库包 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 /** 8 * Lock接口锁的使用 9 * Lock与synchronized最大区别就是:前者更面向对象; 10 * Lock要求程序员手动释放锁,synchronized自动释放。 11 */ 12 public class LockTest { 13 14 public static void main(String[] args) { 15 new LockTest().init(); 16 17 } 18 19 // 该方法的作用是:外部类的静态方法不能实例化内部类对象;所以不能直接在外部类的main实例化,要创建一个中介的普通方法 20 private void init() { 21 final Outputer outputer = new Outputer(); 22 // 线程1 23 new Thread(new Runnable() { 24 25 @Override 26 public void run() { 27 while (true) { 28 try { 29 Thread.sleep(10); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 outputer.output("yangkai"); 34 } 35 } 36 }).start(); 37 // 线程2 38 new Thread(new Runnable() { 39 40 @Override 41 public void run() { 42 while (true) { 43 try { 44 Thread.sleep(10); 45 } catch (InterruptedException e) { 46 e.printStackTrace(); 47 } 48 outputer.output("123456"); 49 } 50 } 51 }).start(); 52 } 53 54 static class Outputer { 55 Lock lock = new ReentrantLock(); 56 57 public void output(String name) { 58 //上锁 59 lock.lock(); 60 try { 61 for (int i = 0; i < name.length(); i++) { 62 // 读取字符串内一个一个的字符 63 System.out.print(name.charAt(i)); 64 } 65 System.out.println(); 66 } finally { 67 //释放锁; 68 /*这里放到finally里的原因是:万一上锁的这个方法中有异常发生; 69 * 那就不执行释放锁代码了,也就是成死锁了;好比你上厕所晕里面了; 70 * 后面的人等啊等的永远进不去似的 71 */ 72 lock.unlock(); 73 } 74 } 75 76 } 77 78 /* 79 * 如果不使用线程锁Lock会出现以下情况: yangkai 123456 yangkai 1y2a3n4g5k6 ai 80 */ 81 } 82 3、读写锁的案例 83 package com.java5.thread.newSkill; 84 85 import java.util.Random; 86 import java.util.concurrent.locks.ReadWriteLock; 87 import java.util.concurrent.locks.ReentrantReadWriteLock; 88 89 /** 90 * 读写锁的案例 91 */ 92 public class ReadWriteLockTest { 93 94 public static void main(String[] args) { 95 96 final Queues queues = new Queues(); 97 for ( int i = 0; i < 10; i++) { 98 final int j = i; 99 new Thread() { 100 public void run() { 101 //此处打标记A,下面注释会提到 102 /*if(j<10)*/ while(true){ 103 queues.get(); 104 } 105 } 106 }.start(); 107 new Thread() { 108 public void run() { 109 /*if(j<10)*/while(true) { 110 queues.put(new Random().nextInt(10000)); 111 } 112 } 113 }.start(); 114 } 115 } 116 } 117 118 class Queues { 119 // 共享数据;只能有一个线程能写改数据,但能有多个线程同时读数据 120 private Object data = null; 121 /*这里如果这么写: 122 * ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 123 上面的标记A处,如果用while(true)会一直写不读,但是如果不用while死循环则可以正确执行, 124 比如用if(i<10);目前还没找到原因,希望大牛们看到后指点迷津; 125 个人猜测:没有用面向接口编程,上锁后,死循环中的内容会无穷之的执行,执行不完不会开锁 126 */ 127 ReadWriteLock rwl = new ReentrantReadWriteLock(); 128 129 // 读的方法,用的是读锁readLock() 130 public void get() { 131 rwl.readLock().lock(); 132 try { 133 System.out.println(Thread.currentThread().getName() 134 + " be ready to read data!"); 135 Thread.sleep((long) Math.random() * 1000); 136 System.out.println(Thread.currentThread().getName() 137 + " have read data:" + data); 138 } catch (InterruptedException e) { 139 e.printStackTrace(); 140 } finally { 141 rwl.readLock().unlock(); 142 } 143 } 144 145 // 写的方法;用到写的锁:writeLock() 146 public void put(Object data) { 147 rwl.writeLock().lock(); 148 try { 149 System.out.println(Thread.currentThread().getName() 150 + " be ready to write data!"); 151 Thread.sleep((long) Math.random() * 1000); 152 this.data = data; 153 System.out.println(Thread.currentThread().getName() 154 + " have write data:" + data); 155 } catch (InterruptedException e) { 156 e.printStackTrace(); 157 } finally { 158 rwl.writeLock().unlock(); 159 } 160 } 161 } 162 4. 缓存系统的模拟编写;读写锁的实际应用价值 163 package com.java5.thread.newSkill; 164 165 import java.util.HashMap; 166 import java.util.Map; 167 import java.util.concurrent.locks.ReadWriteLock; 168 import java.util.concurrent.locks.ReentrantReadWriteLock; 169 170 public class CacheDemo { 171 172 /** 173 * 缓存系统的模拟编写;读写锁的实际应用价值 174 */ 175 private Map<String, Object> cache = new HashMap<String, Object>(); 176 177 public static void main(String[] args) { 178 179 } 180 181 private ReadWriteLock rwl = new ReentrantReadWriteLock(); 182 183 public Object getData(String key) { 184 //如果客户一来读取value数据,则在客户一进去后上一把读锁;防止其他客户再次进行读,产生并发问题 185 rwl.readLock().lock(); 186 Object value = null; 187 try { 188 value = cache.get(key); 189 if (value == null) { 190 //如果如果读到的值为空则释放读锁,打开写锁,准备给value赋值 191 rwl.readLock().unlock(); 192 rwl.writeLock().lock(); 193 try { 194 //如果打开写锁还为空,则给value赋值aaa 195 if (value == null) { 196 value = "aaa"; //实际失去queryDB() 197 } 198 } finally { 199 //使用完写锁后关掉 200 rwl.writeLock().unlock(); 201 } 202 //释放写锁后,再次打开读锁,供客户读取value的数据 203 rwl.readLock().lock(); 204 } 205 } finally { 206 //最后客户一读完后释放掉读锁 207 rwl.readLock().unlock(); 208 } 209 return value; 210 } 211 } 212 5.新技术condition案例分析;代替wait()和notify()方法 213 package com.java5.thread.newSkill; 214 215 import java.util.concurrent.locks.Condition; 216 import java.util.concurrent.locks.Lock; 217 import java.util.concurrent.locks.ReentrantLock; 218 219 /** 220 * 面试题: 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次, 接着再回到主线程又循环100次,如此循环50次,代码如下: 221 * 222 * 思路: 编写一个业务类,是为了不把自己绕进去,体现代码的的高聚类特性和代码的健壮性, 223 * 即共同数据(比如这里的同步锁)或共同算法的若干个方法都可以提到同一个类中编写 224 * 225 * 注意:wait()和notify()必须在synchronized关键字内使用; 226 * 因为this.watit()中用到的this是synchronized()括号内的 227 * 内容;如果不使用synchronized直接就用wait()会包状态不对的错误 228 */ 229 public class ConditionCommunication { 230 231 public static void main(String[] args) { 232 final Business business = new Business(); 233 new Thread(new Runnable() { 234 235 @Override 236 public void run() { 237 for (int i = 1; i <= 50; i++) { 238 business.sub(i); 239 } 240 } 241 }).start(); 242 243 for (int i = 1; i <= 50; i++) { 244 business.main(i); 245 } 246 247 } 248 } 249 250 // 编写一个有子方法(用来调用子线程)和主方法(调用主线程)的业务类 251 252 class Business { 253 private boolean bShouldSub = true; 254 Lock lock = new ReentrantLock(); 255 // condition必须基于lock锁之上的 256 Condition condition = lock.newCondition(); 257 258 public void sub(int i) { 259 try { 260 lock.lock(); 261 while (!bShouldSub) { 262 try { 263 // this.wait(); 264 /* 265 * condition使用的是await()注意与wait()的区别; 266 * 因为condition也是Object对象所以也可以调用wait()方法,所以千万别调错了 267 */ 268 condition.await(); 269 } catch (Exception e) { 270 e.printStackTrace(); 271 } 272 } 273 for (int j = 1; j <= 10; j++) { 274 System.out.println("sub thread sequence of " + j 275 + " ,loop of " + i); 276 } 277 bShouldSub = false; 278 // this.notify(); 279 condition.signal(); 280 } finally { 281 lock.unlock(); 282 } 283 } 284 285 public void main(int i) { 286 /* 287 * 这里最好用while,但是跟if的效果一样,只是前者代码更健壮, while可以防止线程自己唤醒自己,即通常所说的伪唤醒; 288 * 相当于一个人做梦不是被别人叫醒而是自己做噩梦突然惊醒; 这时用while可以防止这种情况发生 289 */ 290 try { 291 lock.lock(); 292 while (bShouldSub) { 293 try { 294 // this.wait(); 295 condition.await(); 296 } catch (Exception e) { 297 e.printStackTrace(); 298 } 299 } 300 for (int j = 1; j <= 100; j++) { 301 System.out.println("main thread sequence of " + j 302 + " ,loop of " + i); 303 } 304 bShouldSub = true; 305 // this.notify(); 306 condition.signal(); 307 } finally { 308 lock.unlock(); 309 } 310 } 311 } 312 6. 多个Condition的应用场景;以下是三个condition通讯的代码: 313 package com.java5.thread.newSkill; 314 315 import java.util.concurrent.locks.Condition; 316 import java.util.concurrent.locks.Lock; 317 import java.util.concurrent.locks.ReentrantLock; 318 319 /** 320 * 多个Condition的应用场景 321 * 以下是三个condition通讯的代码: 322 */ 323 public class ThreeConditionCommunication { 324 325 public static void main(String[] args) { 326 final Business business = new Business(); 327 //线程2,老二线程 328 new Thread(new Runnable() { 329 330 @Override 331 public void run() { 332 for (int i = 1; i <= 50; i++) { 333 business.sub(i); 334 } 335 } 336 }).start(); 337 338 //线程3,老三线程 339 new Thread(new Runnable() { 340 341 @Override 342 public void run() { 343 for (int i = 1; i <= 50; i++) { 344 business.sub2(i); 345 } 346 } 347 }).start(); 348 349 //主线程1,老大线程 350 for (int i = 1; i <= 50; i++) { 351 business.main(i); 352 } 353 354 } 355 356 /*编写一个有子方法(用来调用子线程)和主方法(调用主线程)的业务类 357 * 这个项目下虽然有两个Business类;但是在不同包下所以不影响; 如果在同一包下,那么就要改名或者将其弄成内部类,如果又想要把他当外部类使用, 358 * 那么将其弄成static 静态的就可以了 359 */ 360 static class Business { 361 private int shouldSub = 1; 362 Lock lock = new ReentrantLock(); 363 364 Condition condition1 = lock.newCondition(); 365 Condition condition2 = lock.newCondition(); 366 Condition condition3 = lock.newCondition(); 367 368 public void sub(int i) { 369 try { 370 lock.lock(); 371 while (shouldSub != 2) { 372 try { 373 condition2.await(); 374 } catch (Exception e) { 375 e.printStackTrace(); 376 } 377 } 378 for (int j = 1; j <= 10; j++) { 379 System.out.println("sub thread sequence of " + j 380 + " ,loop of " + i); 381 } 382 shouldSub = 3; 383 condition3.signal(); 384 } finally { 385 lock.unlock(); 386 } 387 } 388 public void sub2(int i) { 389 try { 390 lock.lock(); 391 while (shouldSub != 3) { 392 try { 393 condition3.await(); 394 } catch (Exception e) { 395 e.printStackTrace(); 396 } 397 } 398 for (int j = 1; j <= 10; j++) { 399 System.out.println("sub2 thread sequence of " + j 400 + " ,loop of " + i); 401 } 402 shouldSub = 1; 403 condition1.signal(); 404 } finally { 405 lock.unlock(); 406 } 407 } 408 409 public void main(int i) { 410 try { 411 lock.lock(); 412 while (shouldSub != 1) { 413 try {; 414 condition1.await(); 415 } catch (Exception e) { 416 e.printStackTrace(); 417 } 418 } 419 for (int j = 1; j <= 100; j++) { 420 System.out.println("main thread sequence of " + j 421 + " ,loop of " + i); 422 } 423 shouldSub = 2; 424 condition2.signal(); 425 } finally { 426 lock.unlock(); 427 } 428 } 429 } 430 }