Java线程详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程详解相关的知识,希望对你有一定的参考价值。

   1 Java线程:概念与原理
   2 一、操作系统中线程和进程的概念
   3 
   4 
   5 现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。
   6 
   7 
   8 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
   9 
  10  
  11 
  12 线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
  13 
  14  
  15 
  16 “同时”执行是人的感觉,在线程之间实际上轮换执行。
  17 
  18  
  19 
  20 二、Java中的线程
  21 
  22 
  23 在Java中,“线程”指两件不同的事情:
  24 1、java.lang.Thread类的一个实例;
  25 
  26 2、线程的执行。
  27 
  28  
  29 
  30 使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
  31 
  32  
  33 
  34 一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。
  35 
  36  
  37 
  38 Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。
  39 
  40  
  41 
  42 一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。
  43 
  44  
  45 
  46 一旦创建一个新的线程,就产生一个新的调用栈。
  47 
  48  
  49 
  50 线程总体分两类:用户线程和守候线程。
  51 
  52 
  53 当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的
  54 
  55 Java线程:创建与启动
  56 一、定义线程
  57 
  58  
  59 
  60 1、扩展java.lang.Thread类。
  61 
  62  
  63 
  64 此类中有个run()方法,应该注意其用法:
  65 
  66 public void run()
  67 
  68 如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。
  69 
  70  
  71 
  72 Thread的子类应该重写该方法。
  73 
  74 2、实现java.lang.Runnable接口。
  75 
  76  
  77 
  78 void run()
  79 
  80 使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。
  81 
  82  
  83 
  84 方法run的常规协定是,它可能执行任何所需的操作。
  85 
  86  
  87 
  88 二、实例化线程
  89 
  90  
  91 
  92 1、如果是扩展java.lang.Thread类的线程,则直接new即可。
  93 
  94  
  95 
  96 2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:
  97 
  98 Thread(Runnable target) 
  99 Thread(Runnable target, String name) 
 100 Thread(ThreadGroup group, Runnable target) 
 101 Thread(ThreadGroup group, Runnable target, String name) 
 102 Thread(ThreadGroup group, Runnable target, String name, long stackSize)
 103 
 104  
 105 
 106 三、启动线程
 107 
 108  
 109 
 110 在线程的Thread对象上调用start()方法,而不是run()或者别的方法。
 111 
 112  
 113 
 114 在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
 115 
 116  
 117 
 118 在调用start()方法之后:发生了一系列复杂的事情
 119 
 120 启动新的执行线程(具有新的调用栈);
 121 
 122 该线程从新状态转移到可运行状态;
 123 
 124 当该线程获得机会执行时,其目标run()方法将运行。
 125 
 126  
 127 
 128 注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。
 129 
 130  
 131 
 132  
 133 
 134 四、例子
 135 
 136  
 137 
 138 1、实现Runnable接口的多线程例子
 139 
 140 /** 
 141 * 实现Runnable接口的类
 142 * 
 143 * @author leizhimin 2008-9-13 18:12:10 
 144 */ 
 145 publicclass DoSomethingimplements Runnable {
 146     private String name;
 147 
 148     public DoSomething(String name) {
 149         this.name = name;
 150     } 
 151 
 152     publicvoid run() {
 153         for (int i = 0; i < 5; i++) {
 154             for (long k = 0; k < 100000000; k++) ;
 155             System.out.println(name + ": " + i);
 156         } 
 157     } 
 158 }
 159 
 160  
 161 
 162 /** 
 163 * 测试Runnable类实现的多线程程序
 164 * 
 165 * @author leizhimin 2008-9-13 18:15:02 
 166 */ 
 167 publicclass TestRunnable {
 168     publicstaticvoid main(String[] args) {
 169         DoSomething ds1 = new DoSomething("阿三");
 170         DoSomething ds2 = new DoSomething("李四");
 171 
 172         Thread t1 = new Thread(ds1);
 173         Thread t2 = new Thread(ds2);
 174 
 175         t1.start(); 
 176         t2.start(); 
 177     } 
 178 }
 179 
 180  
 181 
 182 执行结果:
 183 
 184 李四: 0
 185 阿三: 0 
 186 李四: 1 
 187 阿三: 1 
 188 李四: 2 
 189 李四: 3 
 190 阿三: 2 
 191 李四: 4 
 192 阿三: 3 
 193 阿三: 4 
 194 
 195 Process finished with exit code 0
 196 
 197  
 198 
 199 2、扩展Thread类实现的多线程例子
 200 
 201  
 202 
 203 /** 
 204 * 测试扩展Thread类实现的多线程程序
 205 * 
 206 * @author leizhimin 2008-9-13 18:22:13 
 207 */ 
 208 publicclass TestThreadextends Thread{ 
 209     public TestThread(String name) {
 210         super(name);
 211     } 
 212 
 213     publicvoid run() {
 214         for(int i = 0;i<5;i++){
 215             for(long k= 0; k <100000000;k++);
 216             System.out.println(this.getName()+" :"+i);
 217         } 
 218     } 
 219 
 220     publicstaticvoid main(String[] args) {
 221         Thread t1 = new TestThread("阿三");
 222         Thread t2 = new TestThread("李四");
 223         t1.start(); 
 224         t2.start(); 
 225     } 
 226 }
 227 
 228  
 229 
 230 执行结果:
 231 
 232 阿三 :0
 233 李四 :0 
 234 阿三 :1 
 235 李四 :1 
 236 阿三 :2 
 237 李四 :2 
 238 阿三 :3 
 239 阿三 :4 
 240 李四 :3 
 241 李四 :4 
 242 
 243 Process finished with exit code 0
 244 
 245  
 246 
 247 对于上面的多线程程序代码来说,输出的结果是不确定的。其中的一条语句for(long k= 0; k <100000000;k++);是用来模拟一个非常耗时的操作的。
 248 
 249  
 250 
 251 五、一些常见问题
 252 
 253  
 254 
 255 1、线程的名字,一个运行中的线程总是有名字的,名字有两个来源,一个是虚拟机自己给的名字,一个是你自己的定的名字。在没有指定线程名字的情况下,虚拟机总会为线程指定名字,并且主线程的名字总是mian,非主线程的名字不确定。
 256 
 257 2、线程都可以设置名字,也可以获取线程的名字,连主线程也不例外。
 258 
 259 3、获取当前线程的对象的方法是:Thread.currentThread();
 260 
 261 4、在上面的代码中,只能保证:每个线程都将启动,每个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。
 262 
 263 5、当线程目标run()方法结束时该线程完成。
 264 
 265 6、一旦线程启动,它就永远不能再重新启动。只有一个新的线程可以被启动,并且只能一次。一个可运行的线程或死线程可以被重新启动。
 266 
 267 7、线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪个处于可运行状态的线程。
 268 
 269 众多可运行线程中的某一个会被选中做为当前线程。可运行线程被选择运行的顺序是没有保障的。
 270 
 271 8、尽管通常采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,我们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列唱呢个一个队列的事实。
 272 
 273 9、尽管我们没有无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
 274 
 275 Java线程:线程栈模型与线程的变量
 276 要理解线程调度的原理,以及线程执行过程,必须理解线程栈模型。
 277 
 278 线程栈是指某时刻时内存中线程调度的栈信息,当前调用的方法总是位于栈顶。线程栈的内容是随着程序的运行动态变化的,因此研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方)。
 279 
 280  
 281 
 282 下面通过一个示例性的代码说明线程(调用)栈的变化过程。
 283 
 284  
 285 
 286  
 287 
 288 这幅图描述在代码执行到两个不同时刻1、2时候,虚拟机线程调用栈示意图。
 289 
 290  
 291 
 292 当程序执行到t.start();时候,程序多出一个分支(增加了一个调用栈B),这样,栈A、栈B并行执行。
 293 
 294  
 295 
 296 从这里就可以看出方法调用和线程启动的区别了。
 297 
 298 Java线程:线程状态的转换
 299 一、线程状态
 300 
 301  
 302 
 303 线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。用一个图来描述如下:
 304 
 305  
 306 
 307 1、新状态:线程对象已经创建,还没有在其上调用start()方法。
 308 
 309  
 310 
 311 2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
 312 
 313  
 314 
 315 3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
 316 
 317  
 318 
 319 4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。
 320 
 321  
 322 
 323 5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
 324 
 325  
 326 
 327 有关详细状态转换图可以参看本人的“Java多线程编程总结”中的图
 328 
 329  
 330 
 331 二、阻止线程执行
 332 
 333 对于线程的阻止,考虑一下三个方面,不考虑IO阻塞的情况:
 334 
 335 睡眠;
 336 
 337 等待;
 338 
 339 因为需要一个对象的锁定而被阻塞。
 340 
 341  
 342 
 343 1、睡眠
 344 
 345 Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。
 346 
 347  
 348 
 349 线程睡眠的原因:线程执行太快,或者需要强制进入下一轮,因为Java规范不保证合理的轮换。
 350 
 351  
 352 
 353 睡眠的实现:调用静态方法。
 354 
 355         try {
 356             Thread.sleep(123);
 357         } catch (InterruptedException e) {
 358             e.printStackTrace();  
 359         }
 360 
 361  
 362 
 363 睡眠的位置:为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠。
 364 
 365  
 366 
 367 例如,在前面的例子中,将一个耗时的操作改为睡眠,以减慢线程的执行。可以这么写:
 368 
 369  
 370 
 371     public void run() {
 372         for(int i = 0;i<5;i++){
 373 
 374 // 很耗时的操作,用来减慢线程的执行
 375 //            for(long k= 0; k <100000000;k++);
 376             try {
 377                 Thread.sleep(3);
 378             } catch (InterruptedException e) {
 379                 e.printStackTrace();  .
 380             }
 381             System.out.println(this.getName()+" :"+i);
 382         }
 383     }
 384 
 385  
 386 
 387 运行结果:
 388 
 389 阿三 :0
 390 李四 :0 
 391 阿三 :1 
 392 阿三 :2 
 393 阿三 :3 
 394 李四 :1 
 395 李四 :2 
 396 阿三 :4 
 397 李四 :3 
 398 李四 :4 
 399 
 400 Process finished with exit code 0
 401 
 402  
 403 
 404 这样,线程在每次执行过程中,总会睡眠3毫秒,睡眠了,其他的线程就有机会执行了。
 405 
 406  
 407 
 408 注意:
 409 
 410 1、线程睡眠是帮助所有线程获得运行机会的最好方法。
 411 
 412 2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
 413 
 414 3、sleep()是静态方法,只能控制当前正在运行的线程。
 415 
 416  
 417 
 418 下面给个例子:
 419 
 420 /** 
 421 * 一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串
 422 * 
 423 * @author leizhimin 2008-9-14 9:53:49 
 424 */ 
 425 publicclass MyThreadextends Thread {
 426 
 427     publicvoid run() {
 428         for (int i = 0; i < 100; i++) {
 429             if ((i) % 10 == 0) {
 430                 System.out.println("-------" + i);
 431             } 
 432             System.out.print(i); 
 433             try {
 434                 Thread.sleep(1); 
 435                 System.out.print("    线程睡眠1毫秒!\n");
 436             } catch (InterruptedException e) {
 437                 e.printStackTrace(); 
 438             } 
 439         } 
 440     } 
 441 
 442     publicstaticvoid main(String[] args) {
 443         new MyThread().start();
 444     } 
 445 }
 446 
 447  
 448 
 449 -------0 
 450 0    线程睡眠1毫秒!
 451 1    线程睡眠1毫秒!
 452 2    线程睡眠1毫秒!
 453 3    线程睡眠1毫秒!
 454 4    线程睡眠1毫秒!
 455 5    线程睡眠1毫秒!
 456 6    线程睡眠1毫秒!
 457 7    线程睡眠1毫秒!
 458 8    线程睡眠1毫秒!
 459 9    线程睡眠1毫秒!
 460 -------10 
 461 10    线程睡眠1毫秒!
 462 11    线程睡眠1毫秒!
 463 12    线程睡眠1毫秒!
 464 13    线程睡眠1毫秒!
 465 14    线程睡眠1毫秒!
 466 15    线程睡眠1毫秒!
 467 16    线程睡眠1毫秒!
 468 17    线程睡眠1毫秒!
 469 18    线程睡眠1毫秒!
 470 19    线程睡眠1毫秒!
 471 -------20 
 472 20    线程睡眠1毫秒!
 473 21    线程睡眠1毫秒!
 474 22    线程睡眠1毫秒!
 475 23    线程睡眠1毫秒!
 476 24    线程睡眠1毫秒!
 477 25    线程睡眠1毫秒!
 478 26    线程睡眠1毫秒!
 479 27    线程睡眠1毫秒!
 480 28    线程睡眠1毫秒!
 481 29    线程睡眠1毫秒!
 482 -------30 
 483 30    线程睡眠1毫秒!
 484 31    线程睡眠1毫秒!
 485 32    线程睡眠1毫秒!
 486 33    线程睡眠1毫秒!
 487 34    线程睡眠1毫秒!
 488 35    线程睡眠1毫秒!
 489 36    线程睡眠1毫秒!
 490 37    线程睡眠1毫秒!
 491 38    线程睡眠1毫秒!
 492 39    线程睡眠1毫秒!
 493 -------40 
 494 40    线程睡眠1毫秒!
 495 41    线程睡眠1毫秒!
 496 42    线程睡眠1毫秒!
 497 43    线程睡眠1毫秒!
 498 44    线程睡眠1毫秒!
 499 45    线程睡眠1毫秒!
 500 46    线程睡眠1毫秒!
 501 47    线程睡眠1毫秒!
 502 48    线程睡眠1毫秒!
 503 49    线程睡眠1毫秒!
 504 -------50 
 505 50    线程睡眠1毫秒!
 506 51    线程睡眠1毫秒!
 507 52    线程睡眠1毫秒!
 508 53    线程睡眠1毫秒!
 509 54    线程睡眠1毫秒!
 510 55    线程睡眠1毫秒!
 511 56    线程睡眠1毫秒!
 512 57    线程睡眠1毫秒!
 513 58    线程睡眠1毫秒!
 514 59    线程睡眠1毫秒!
 515 -------60 
 516 60    线程睡眠1毫秒!
 517 61    线程睡眠1毫秒!
 518 62    线程睡眠1毫秒!
 519 63    线程睡眠1毫秒!
 520 64    线程睡眠1毫秒!
 521 65    线程睡眠1毫秒!
 522 66    线程睡眠1毫秒!
 523 67    线程睡眠1毫秒!
 524 68    线程睡眠1毫秒!
 525 69    线程睡眠1毫秒!
 526 -------70 
 527 70    线程睡眠1毫秒!
 528 71    线程睡眠1毫秒!
 529 72    线程睡眠1毫秒!
 530 73    线程睡眠1毫秒!
 531 74    线程睡眠1毫秒!
 532 75    线程睡眠1毫秒!
 533 76    线程睡眠1毫秒!
 534 77    线程睡眠1毫秒!
 535 78    线程睡眠1毫秒!
 536 79    线程睡眠1毫秒!
 537 -------80 
 538 80    线程睡眠1毫秒!
 539 81    线程睡眠1毫秒!
 540 82    线程睡眠1毫秒!
 541 83    线程睡眠1毫秒!
 542 84    线程睡眠1毫秒!
 543 85    线程睡眠1毫秒!
 544 86    线程睡眠1毫秒!
 545 87    线程睡眠1毫秒!
 546 88    线程睡眠1毫秒!
 547 89    线程睡眠1毫秒!
 548 -------90 
 549 90    线程睡眠1毫秒!
 550 91    线程睡眠1毫秒!
 551 92    线程睡眠1毫秒!
 552 93    线程睡眠1毫秒!
 553 94    线程睡眠1毫秒!
 554 95    线程睡眠1毫秒!
 555 96    线程睡眠1毫秒!
 556 97    线程睡眠1毫秒!
 557 98    线程睡眠1毫秒!
 558 99    线程睡眠1毫秒!
 559 
 560 Process finished with exit code 0
 561 
 562 
 563 2、线程的优先级和线程让步yield()
 564 
 565 线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。
 566 
 567  
 568 
 569 要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。
 570 
 571  
 572 
 573 注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。
 574 
 575  
 576 
 577 当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。
 578 
 579  
 580 
 581 设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:
 582 
 583         Thread t = new MyThread();
 584         t.setPriority(8);
 585         t.start();
 586 
 587 线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。
 588 
 589  
 590 
 591 线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:
 592 
 593 static int MAX_PRIORITY 
 594           线程可以具有的最高优先级。
 595 static int MIN_PRIORITY 
 596           线程可以具有的最低优先级。
 597 static int NORM_PRIORITY 
 598           分配给线程的默认优先级。
 599 
 600  
 601 
 602 3、Thread.yield()方法
 603 
 604  
 605 
 606 Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
 607 
 608 yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
 609 
 610 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
 611 
 612  
 613 
 614 4、join()方法
 615 
 616  
 617 
 618 Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。例如:
 619 
 620         Thread t = new MyThread();
 621         t.start();
 622         t.join();
 623 
 624 另外,join()方法还有带超时限制的重载版本。例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。
 625 
 626  
 627 
 628 线程的加入join()对线程栈导致的结果是线程栈发生了变化,当然这些变化都是瞬时的。下面给示意图:
 629 
 630  
 631 
 632  
 633 
 634  
 635 
 636 小结
 637 
 638 到目前位置,介绍了线程离开运行状态的3种方法:
 639 
 640 1、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。
 641 
 642 2、调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。
 643 
 644 3、调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
 645 
 646  
 647 
 648 除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:
 649 
 650 1、线程的run()方法完成。
 651 
 652 2、在对象上调用wait()方法(不是在线程上调用)。
 653 
 654 3、线程不能在对象上获得锁定,它正试图运行该对象的方法代码。
 655 
 656 4、线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。
 657 
 658 Java线程:线程的同步与锁
 659 一、同步问题提出
 660 
 661  
 662 
 663 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
 664 
 665 例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。
 666 
 667  
 668 
 669 publicclass Foo {
 670     privateint x = 100;
 671 
 672     publicint getX() {
 673         return x;
 674     } 
 675 
 676     publicint fix(int y) {
 677         x = x - y; 
 678         return x;
 679     } 
 680 }
 681 
 682  
 683 
 684 publicclass MyRunnableimplements Runnable {
 685     private Foo foo =new Foo(); 
 686 
 687     publicstaticvoid main(String[] args) {
 688         MyRunnable r = new MyRunnable();
 689         Thread ta = new Thread(r,"Thread-A"); 
 690         Thread tb = new Thread(r,"Thread-B"); 
 691         ta.start(); 
 692         tb.start(); 
 693     } 
 694 
 695     publicvoid run() {
 696         for (int i = 0; i < 3; i++) {
 697             this.fix(30);
 698             try {
 699                 Thread.sleep(1); 
 700             } catch (InterruptedException e) {
 701                 e.printStackTrace(); 
 702             } 
 703             System.out.println(Thread.currentThread().getName() + " :当前foo对象的x值= " + foo.getX());
 704         } 
 705     } 
 706 
 707     publicint fix(int y) {
 708         return foo.fix(y);
 709     } 
 710 }
 711 
 712  
 713 
 714 运行结果:
 715 
 716 Thread-A : 当前foo对象的x值= 40
 717 Thread-B : 当前foo对象的x值= 40
 718 Thread-B : 当前foo对象的x值= -20
 719 Thread-A : 当前foo对象的x值= -50
 720 Thread-A : 当前foo对象的x值= -80
 721 Thread-B : 当前foo对象的x值= -80
 722 
 723 Process finished with exit code 0
 724 
 725  
 726 
 727 从结果发现,这样的输出值明显是不合理的。原因是两个线程不加控制的访问Foo对象并修改其数据所致。
 728 
 729  
 730 
 731 如果要保持结果的合理性,只需要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了。
 732 
 733  
 734 
 735 在具体的Java代码中需要完成一下两个操作:
 736 
 737 把竞争访问的资源类Foo变量x标识为private;
 738 
 739 同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
 740 
 741  
 742 
 743 二、同步和锁定
 744 
 745  
 746 
 747 1、锁的原理
 748 
 749  
 750 
 751 Java中每个对象都有一个内置锁
 752 
 753  
 754 
 755 当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
 756 
 757  
 758 
 759 当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
 760 
 761  
 762 
 763 一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
 764 
 765  
 766 
 767 释放锁是指持锁线程退出了synchronized同步方法或代码块。
 768 
 769  
 770 
 771 关于锁和同步,有一下几个要点:
 772 
 773 1)、只能同步方法,而不能同步变量和类;
 774 
 775 2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
 776 
 777 3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
 778 
 779 4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
 780 
 781 5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
 782 
 783 6)、线程睡眠时,它所持的任何锁都不会释放。
 784 
 785 7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
 786 
 787 8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
 788 
 789 9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
 790 
 791     public int fix(int y) {
 792         synchronized (this) {
 793             x = x - y;
 794         }
 795         return x;
 796     }
 797 
 798  
 799 
 800 当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:
 801 
 802     public synchronized int getX() {
 803         return x++;
 804     }
 805 
 806  807 
 808     public int getX() {
 809         synchronized (this) {
 810             return x;
 811         }
 812     }
 813 
 814 效果是完全一样的。
 815 
 816  
 817 
 818 三、静态方法同步
 819 
 820  
 821 
 822 要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。
 823 
 824 例如:
 825 
 826 public static synchronized int setName(String name){
 827 
 828       Xxx.name = name;
 829 
 830 }
 831 
 832 等价于
 833 public static int setName(String name){
 834       synchronized(Xxx.class){
 835             Xxx.name = name;
 836       }
 837 }
 838 
 839 
 840  
 841 
 842 四、如果线程不能不能获得锁会怎么样
 843 
 844  
 845 
 846 如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。
 847 
 848  
 849 
 850 当考虑阻塞时,一定要注意哪个对象正被用于锁定:
 851 
 852 1、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
 853 
 854  
 855 
 856 2、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
 857 
 858  
 859 
 860 3、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
 861 
 862  
 863 
 864 4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。
 865 
 866  
 867 
 868 五、何时需要同步
 869 
 870  
 871 
 872 在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。
 873 
 874  
 875 
 876 对于非静态字段中可更改的数据,通常使用非静态方法访问。
 877 
 878 对于静态字段中可更改的数据,通常使用静态方法访问。
 879 
 880  
 881 
 882 如果需要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得非常复杂。已经超出SJCP考试范围了。
 883 
 884  
 885 
 886 六、线程安全类
 887 
 888  
 889 
 890 当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。
 891 
 892  
 893 
 894 即使是线程安全类,也应该特别小心,因为操作的线程是间仍然不一定安全。
 895 
 896  
 897 
 898 举个形象的例子,比如一个集合是线程安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候。第二个线程也来执行与第一个线程相同的操作,也许在第一个线程查询后,第二个线程也查询出集合非空,但是当第一个执行清除后,第二个再执行删除显然是不对的,因为此时集合已经为空了。
 899 
 900 看个代码:
 901 
 902  
 903 
 904 publicclass NameList {
 905     private List nameList = Collections.synchronizedList(new LinkedList());
 906 
 907     publicvoid add(String name) {
 908         nameList.add(name); 
 909     } 
 910 
 911     public String removeFirst() {
 912         if (nameList.size() > 0) {
 913             return (String) nameList.remove(0);
 914         } else {
 915             returnnull;
 916         } 
 917     } 
 918 }
 919 
 920  
 921 
 922 publicclass Test {
 923     publicstaticvoid main(String[] args) {
 924         final NameList nl =new NameList(); 
 925         nl.add("aaa");
 926         class NameDropperextends Thread{ 
 927             publicvoid run(){
 928                 String name = nl.removeFirst(); 
 929                 System.out.println(name); 
 930             } 
 931         } 
 932 
 933         Thread t1 = new NameDropper();
 934         Thread t2 = new NameDropper();
 935         t1.start(); 
 936         t2.start(); 
 937     } 
 938 }
 939 
 940  
 941 
 942 虽然集合对象
 943 
 944     private List nameList = Collections.synchronizedList(new LinkedList());
 945 是同步的,但是程序还不是线程安全的。
 946 
 947 出现这种事件的原因是,上例中一个线程操作列表过程中无法阻止另外一个线程对列表的其他操作。
 948 
 949  
 950 
 951 解决上面问题的办法是,在操作集合对象的NameList上面做一个同步。改写后的代码如下:
 952 
 953 publicclass NameList {
 954     private List nameList = Collections.synchronizedList(new LinkedList());
 955 
 956     publicsynchronizedvoid add(String name) {
 957         nameList.add(name); 
 958     } 
 959 
 960     publicsynchronized String removeFirst() {
 961         if (nameList.size() > 0) {
 962             return (String) nameList.remove(0);
 963         } else {
 964             returnnull;
 965         } 
 966     } 
 967 }
 968 
 969  
 970 
 971 这样,当一个线程访问其中一个同步方法时,其他线程只有等待。
 972 
 973  
 974 
 975 七、线程死锁
 976 
 977  
 978 
 979 死锁对Java程序来说,是很复杂的,也很难发现问题。当两个线程被阻塞,每个线程在等待另一个线程时就发生死锁。
 980 
 981  
 982 
 983 还是看一个比较直观的死锁例子:
 984 
 985  
 986 
 987 publicclass DeadlockRisk {
 988     privatestaticclass Resource {
 989         publicint value;
 990     } 
 991 
 992     private Resource resourceA =new Resource(); 
 993     private Resource resourceB =new Resource(); 
 994 
 995     publicint read() {
 996         synchronized (resourceA) {
 997             synchronized (resourceB) {
 998                 return resourceB.value + resourceA.value;
 999             } 
1000         } 
1001     } 
1002 
1003     publicvoid write(int a,int b) { 
1004         synchronized (resourceB) {
1005             synchronized (resourceA) {
1006                 resourceA.value = a; 
1007                 resourceB.value = b; 
1008             } 
1009         } 
1010     } 
1011 }
1012 
1013  
1014 
1015 假设read()方法由一个线程启动,write()方法由另外一个线程启动。读线程将拥有resourceA锁,写线程将拥有resourceB锁,两者都坚持等待的话就出现死锁。
1016 
1017  
1018 
1019 实际上,上面这个例子发生死锁的概率很小。因为在代码内的某个点,CPU必须从读线程切换到写线程,所以,死锁基本上不能发生。
1020 
1021  
1022 
1023 但是,无论代码中发生死锁的概率有多小,一旦发生死锁,程序就死掉。有一些设计方法能帮助避免死锁,包括始终按照预定义的顺序获取锁这一策略。已经超出SCJP的考试范围。
1024 
1025  
1026 
1027 八、线程同步小结
1028 
1029  
1030 
1031 1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
1032 
1033 2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
1034 
1035 3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
1036 
1037 4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
1038 
1039 5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
1040 
1041 6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
1042 
1043 7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
1044 
1045 Java线程:线程的交互
1046 线程交互是比较复杂的问题,SCJP要求不很基础:给定一个场景,编写代码来恰当使用等待、通知和通知所有线程。
1047 
1048  
1049 
1050 一、线程交互的基础知识
1051 
1052  
1053 
1054 SCJP所要求的线程交互知识点需要从java.lang.Object的类的三个方法来学习:
1055 
1056  
1057 
1058  void notify() 
1059           唤醒在此对象监视器上等待的单个线程。
1060  void notifyAll() 
1061           唤醒在此对象监视器上等待的所有线程。
1062  void wait() 
1063           导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
1064 
1065  
1066 
1067 当然,wait()还有另外两个重载方法:
1068 
1069  void wait(long timeout) 
1070           导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。 
1071  void wait(long timeout, int nanos) 
1072           导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
1073 
1074  
1075 
1076 以上这些方法是帮助线程传递线程关心的时间状态。
1077 
1078  
1079 
1080 关于等待/通知,要记住的关键点是:
1081 
1082 必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
1083 
1084 wait()、notify()、notifyAll()都是Object的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该信号(通知)。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。
1085 
1086  
1087 
1088 下面看个例子就明白了:
1089 
1090 /** 
1091 * 计算输出其他线程锁计算的数据
1092 * 
1093 * @author leizhimin 2008-9-15 13:20:38 
1094 */ 
1095 publicclass ThreadA {
1096     publicstaticvoid main(String[] args) {
1097         ThreadB b = new ThreadB();
1098         //启动计算线程
1099         b.start(); 
1100         //线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者
1101         synchronized (b) {
1102             try {
1103                 System.out.println("等待对象b完成计算。。。");
1104                 //当前线程A等待
1105                 b.wait(); 
1106             } catch (InterruptedException e) {
1107                 e.printStackTrace(); 
1108             } 
1109             System.out.println("b对象计算的总和是:" + b.total);
1110         } 
1111     } 
1112 }
1113 
1114  
1115 
1116 /** 
1117 * 计算1+2+3 ... +100的和
1118 * 
1119 * @author leizhimin 2008-9-15 13:20:49 
1120 */ 
1121 publicclass ThreadBextends Thread {
1122     int total; 
1123 
1124     publicvoid run() {
1125         synchronized (this) {
1126             for (int i = 0; i < 101; i++) {
1127                 total += i; 
1128             } 
1129             //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒
1130             notify(); 
1131         } 
1132     } 
1133 }
1134 
1135  
1136 
1137 等待对象b完成计算。。。
1138 b对象计算的总和是:5050 
1139 
1140 Process finished with exit code 0
1141 
1142  
1143 
1144 千万注意:
1145 
1146 当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。如果线程荣然在完成同步代码,则线程在移出之前不会放弃锁。因此,只要调用notify()并不意味着这时该锁变得可用。
1147 
1148  
1149 
1150 二、多个线程在等待一个对象锁时候使用notifyAll()
1151 
1152  
1153 
1154 在多数情况下,最好通知等待某个对象的所有线程。如果这样做,可以在对象上使用notifyAll()让所有在此对象上等待的线程冲出等待区,返回到可运行状态。
1155 
1156  
1157 
1158 下面给个例子:
1159 
1160 /** 
1161 * 计算线程 
1162 * 
1163 * @author leizhimin 2008-9-20 11:15:46 
1164 */ 
1165 publicclass Calculatorextends Thread {
1166         int total;
1167 
1168         publicvoid run() {
1169                 synchronized (this) {
1170                         for (int i = 0; i < 101; i++) {
1171                                 total += i; 
1172                         } 
1173                 } 
1174                 //通知所有在此对象上等待的线程
1175                 notifyAll(); 
1176         } 
1177 }
1178 
1179  
1180 
1181 /** 
1182 * 获取计算结果并输出 
1183 * 
1184 * @author leizhimin 2008-9-20 11:15:22 
1185 */ 
1186 publicclass ReaderResultextends Thread {
1187         Calculator c; 
1188 
1189         public ReaderResult(Calculator c) {
1190                 this.c = c;
1191         } 
1192 
1193         publicvoid run() {
1194                 synchronized (c) {
1195                         try {
1196                                 System.out.println(Thread.currentThread() + "等待计算结果。。。");
1197                                 c.wait(); 
1198                         } catch (InterruptedException e) {
1199                                 e.printStackTrace(); 
1200                         } 
1201                         System.out.println(Thread.currentThread() + "计算结果为:" + c.total);
1202                 } 
1203         } 
1204 
1205         publicstaticvoid main(String[] args) {
1206                 Calculator calculator = new Calculator();
1207 
1208                 //启动三个线程,分别获取计算结果
1209                 new ReaderResult(calculator).start();
1210                 new ReaderResult(calculator).start();
1211                 new ReaderResult(calculator).start();
1212                 //启动计算线程
1213                 calculator.start(); 
1214         } 
1215 }
1216 
1217  
1218 
1219 运行结果:
1220 
1221 Thread[Thread-1,5,main]等待计算结果。。。
1222 Thread[Thread-2,5,main]等待计算结果。。。
1223 Thread[Thread-3,5,main]等待计算结果。。。
1224 Exception in thread "Thread-0" java.lang.IllegalMonitorStateException: current thread not owner
1225   at java.lang.Object.notifyAll(Native Method) 
1226   at threadtest.Calculator.run(Calculator.java:18) 
1227 Thread[Thread-1,5,main]计算结果为:5050
1228 Thread[Thread-2,5,main]计算结果为:5050
1229 Thread[Thread-3,5,main]计算结果为:5050
1230 
1231 Process finished with exit code 0
1232 
1233  
1234 
1235 运行结果表明,程序中有异常,并且多次运行结果可能有多种输出结果。这就是说明,这个多线程的交互程序还存在问题。究竟是出了什么问题,需要深入的分析和思考,下面将做具体分析。
1236 
1237  
1238 
1239 实际上,上面这个代码中,我们期望的是读取结果的线程在计算线程调用notifyAll()之前等待即可。但是,如果计算线程先执行,并在读取结果线程等待之前调用了notify()方法,那么又会发生什么呢?这种情况是可能发生的。因为无法保证线程的不同部分将按照什么顺序来执行。幸运的是当读取线程运行时,它只能马上进入等待状态----它没有做任何事情来检查等待的事件是否已经发生。  ----因此,如果计算线程已经调用了notifyAll()方法,那么它就不会再次调用notifyAll(),----并且等待的读取线程将永远保持等待。这当然是开发者所不愿意看到的问题。
1240 
1241  
1242 
1243 因此,当等待的事件发生时,需要能够检查notifyAll()通知事件是否已经发生。
1244 
1245  
1246 
1247 通常,解决上面问题的最佳方式是将
1248 
1249 Java线程:线程的调度-休眠
1250 Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。
1251 
1252  
1253 
1254 这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。
1255 
1256  
1257 
1258 线程休眠的目的是使线程让出CPU的最简单的做法之一,线程休眠时候,会将CPU资源交给其他线程,以便能轮换执行,当休眠一定时间后,线程会苏醒,进入准备状态等待执行。
1259 
1260  
1261 
1262 线程休眠的方法是Thread.sleep(long millis)和Thread.sleep(long millis, int nanos),均为静态方法,那调用sleep休眠的哪个线程呢?简单说,哪个线程调用sleep,就休眠哪个线程。
1263 
1264  
1265 
1266 /** 
1267 * Java线程:线程的调度-休眠
1268 * 
1269 * @author leizhimin 2009-11-4 9:02:40 
1270 */ 
1271 publicclass Test {
1272         publicstaticvoid main(String[] args) {
1273                 Thread t1 = new MyThread1();
1274                 Thread t2 = new Thread(new MyRunnable());
1275                 t1.start(); 
1276                 t2.start(); 
1277         } 
1278 } 
1279 
1280 class MyThread1 extends Thread { 
1281         publicvoid run() {
1282                 for (int i = 0; i < 3; i++) {
1283                         System.out.println("线程1第" + i + "次执行!");
1284                         try {
1285                                 Thread.sleep(50); 
1286                         } catch (InterruptedException e) {
1287                                 e.printStackTrace(); 
1288                         } 
1289                 } 
1290         } 
1291 } 
1292 
1293 class MyRunnableimplements Runnable {
1294         publicvoid run() {
1295                 for (int i = 0; i < 3; i++) {
1296                         System.out.println("线程2第" + i + "次执行!");
1297                         try {
1298                                 Thread.sleep(50); 
1299                         } catch (InterruptedException e) {
1300                                 e.printStackTrace(); 
1301                         } 
1302                 } 
1303         } 
1304 }
1305 
1306  
1307 
1308 线程2第0次执行!
1309 线程1第0次执行!
1310 线程1第1次执行!
1311 线程2第1次执行!
1312 线程1第2次执行!
1313 线程2第2次执行!
1314 
1315 Process finished with exit code 0
1316 
1317  
1318 
1319 从上面的结果输出可以看出,无法精准保证线程执行次序。
1320 
1321 Java线程:线程的调度-优先级
1322 与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
1323 
1324  
1325 
1326 线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5。
1327 
1328  
1329 
1330 在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。
1331 
1332  
1333 
1334 /** 
1335 * Java线程:线程的调度-优先级
1336 * 
1337 * @author leizhimin 2009-11-4 9:02:40 
1338 */ 
1339 publicclass Test {
1340         publicstaticvoid main(String[] args) {
1341                 Thread t1 = new MyThread1();
1342                 Thread t2 = new Thread(new MyRunnable());
1343                 t1.setPriority(10); 
1344                 t2.setPriority(1); 
1345 
1346                 t2.start(); 
1347                 t1.start(); 
1348         } 
1349 } 
1350 
1351 class MyThread1 extends Thread { 
1352         publicvoid run() {
1353                 for (int i = 0; i < 10; i++) {
1354                         System.out.println("线程1第" + i + "次执行!");
1355                         try {
1356                                 Thread.sleep(100); 
1357                         } catch (InterruptedException e) {
1358                                 e.printStackTrace(); 
1359                         } 
1360                 } 
1361         } 
1362 } 
1363 
1364 class MyRunnableimplements Runnable {
1365         publicvoid run() {
1366                 for (int i = 0; i < 10; i++) {
1367                         System.out.println("线程2第" + i + "次执行!");
1368                         try {
1369                                 Thread.sleep(100); 
1370                         } catch (InterruptedException e) {
1371                                 e.printStackTrace(); 
1372                         } 
1373                 } 
1374         } 
1375 }
1376 
1377  
1378 
1379 线程1第0次执行!
1380 线程2第0次执行!
1381 线程2第1次执行!
1382 线程1第1次执行!
1383 线程2第2次执行!
1384 线程1第2次执行!
1385 线程1第3次执行!
1386 线程2第3次执行!
1387 线程2第4次执行!
1388 线程1第4次执行!
1389 线程1第5次执行!
1390 线程2第5次执行!
1391 线程1第6次执行!
1392 线程2第6次执行!
1393 线程1第7次执行!
1394 线程2第7次执行!
1395 线程1第8次执行!
1396 线程2第8次执行!
1397 线程1第9次执行!
1398 线程2第9次执行!
1399 
1400 Process finished with exit code 0
1401 
1402 Java线程:线程的调度-让步
1403 线程的让步含义就是使当前运行着线程让出CPU资源,但是然给谁不知道,仅仅是让出,线程状态回到可运行状态。
1404 
1405  
1406 
1407 线程的让步使用Thread.yield()方法,yield()为静态方法,功能是暂停当前正在执行的线程对象,并执行其他线程。
1408 
1409  
1410 
1411 /** 
1412 * Java线程:线程的调度-让步
1413 * 
1414 * @author leizhimin 2009-11-4 9:02:40 
1415 */ 
1416 publicclass Test {
1417         publicstaticvoid main(String[] args) {
1418                 Thread t1 = new MyThread1();
1419                 Thread t2 = new Thread(new MyRunnable());
1420 
1421                 t2.start(); 
1422                 t1.start(); 
1423         } 
1424 } 
1425 
1426 class MyThread1 extends Thread { 
1427         publicvoid run() {
1428                 for (int i = 0; i < 10; i++) {
1429                         System.out.println("线程1第" + i + "次执行!");
1430                 } 
1431         } 
1432 } 
1433 
1434 class MyRunnableimplements Runnable {
1435         publicvoid run() {
1436                 for (int i = 0; i < 10; i++) {
1437                         System.out.println("线程2第" + i + "次执行!");
1438                         Thread.yield(); 
1439                 } 
1440         } 
1441 }
1442 
1443  
1444 
1445 线程2第0次执行!
1446 线程2第1次执行!
1447 线程2第2次执行!
1448 线程2第3次执行!
1449 线程1第0次执行!
1450 线程1第1次执行!
1451 线程1第2次执行!
1452 线程1第3次执行!
1453 线程1第4次执行!
1454 线程1第5次执行!
1455 线程1第6次执行!
1456 线程1第7次执行!
1457 线程1第8次执行!
1458 线程1第9次执行!
1459 线程2第4次执行!
1460 线程2第5次执行!
1461 线程2第6次执行!
1462 线程2第7次执行!
1463 线程2第8次执行!
1464 线程2第9次执行!
1465 
1466 Process finished with exit code 0
1467 
1468 Java线程:线程的调度-合并
1469 线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。
1470 
1471  
1472 
1473 join为非静态方法,定义如下:
1474 
1475 void join()    
1476     等待该线程终止。    
1477 void join(long millis)    
1478     等待该线程终止的时间最长为 millis毫秒。    
1479 void join(long millis,int nanos)    
1480     等待该线程终止的时间最长为 millis毫秒 + nanos 纳秒。
1481 
1482  
1483 
1484 /** 
1485 * Java线程:线程的调度-合并
1486 * 
1487 * @author leizhimin 2009-11-4 9:02:40 
1488 */ 
1489 publicclass Test {
1490         publicstaticvoid main(String[] args) {
1491                 Thread t1 = new MyThread1();
1492                 t1.start(); 
1493 
1494                 for (int i = 0; i < 20; i++) {
1495                         System.out.println("主线程第" + i +"次执行!");
1496                         if (i > 2)try { 
1497                                 //t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
1498                                 t1.join(); 
1499                         } catch (InterruptedException e) {
1500                                 e.printStackTrace(); 
1501                         } 
1502                 } 
1503         } 
1504 } 
1505 
1506 class MyThread1 extends Thread { 
1507         publicvoid run() {
1508                 for (int i = 0; i < 10; i++) {
1509                         System.out.println("线程1第" + i + "次执行!");
1510                 } 
1511         } 
1512 }
1513 
1514  
1515 
1516 主线程第0次执行!
1517 主线程第1次执行!
1518 主线程第2次执行!
1519 线程1第0次执行!
1520 主线程第3次执行!
1521 线程1第1次执行!
1522 线程1第2次执行!
1523 线程1第3次执行!
1524 线程1第4次执行!
1525 线程1第5次执行!
1526 线程1第6次执行!
1527 线程1第7次执行!
1528 线程1第8次执行!
1529 线程1第9次执行!
1530 主线程第4次执行!
1531 主线程第5次执行!
1532 主线程第6次执行!
1533 主线程第7次执行!
1534 主线程第8次执行!
1535 主线程第9次执行!
1536 主线程第10次执行!
1537 主线程第11次执行!
1538 主线程第12次执行!
1539 主线程第13次执行!
1540 主线程第14次执行!
1541 主线程第15次执行!
1542 主线程第16次执行!
1543 主线程第17次执行!
1544 主线程第18次执行!
1545 主线程第19次执行!
1546 
1547 Process finished with exit code 0
1548 
1549 Java线程:线程的调度-守护线程
1550 守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
1551 
1552  
1553 
1554 守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
1555 
1556  
1557 
1558 setDaemon方法的详细说明:
1559 
1560 publicfinalvoid setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。    
1561   该方法必须在启动线程前调用。    
1562 
1563   该方法首先调用该线程的 checkAccess方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。    
1564 
1565 
1566   参数:
1567     on - 如果为true,则将该线程标记为守护线程。    
1568   抛出:    
1569     IllegalThreadStateException - 如果该线程处于活动状态。    
1570     SecurityException - 如果当前线程无法修改该线程。
1571   另请参见:
1572     isDaemon(), checkAccess()
1573 
1574 
1575  
1576 
1577 /** 
1578 * Java线程:线程的调度-守护线程
1579 * 
1580 * @author leizhimin 2009-11-4 9:02:40 
1581 */ 
1582 publicclass Test {
1583         publicstaticvoid main(String[] args) {
1584                 Thread t1 = new MyCommon();
1585                 Thread t2 = new Thread(new MyDaemon());
1586                 t2.setDaemon(true);        //设置为守护线程
1587 
1588                 t2.start(); 
1589                 t1.start(); 
1590         } 
1591 } 
1592 
1593 class MyCommon extends Thread { 
1594         publicvoid run() {
1595                 for (int i = 0; i < 5; i++) {
1596                         System.out.println("线程1第" + i + "次执行!");
1597                         try {
1598                                 Thread.sleep(7); 
1599                         } catch (InterruptedException e) {
1600                                 e.printStackTrace(); 
1601                         } 
1602                 } 
1603         } 
1604 } 
1605 
1606 class MyDaemon implements Runnable { 
1607         publicvoid run() {
1608                 for (long i = 0; i < 9999999L; i++) {
1609                         System.out.println("后台线程第" + i +"次执行!");
1610                         try {
1611                                 Thread.sleep(7); 
1612                         } catch (InterruptedException e) {
1613                                 e.printStackTrace(); 
1614                         } 
1615                 } 
1616         } 
1617 }
1618 
1619  
1620 
1621 后台线程第0次执行!
1622 线程1第0次执行!
1623 线程1第1次执行!
1624 后台线程第1次执行!
1625 后台线程第2次执行!
1626 线程1第2次执行!
1627 线程1第3次执行!
1628 后台线程第3次执行!
1629 线程1第4次执行!
1630 后台线程第4次执行!
1631 后台线程第5次执行!
1632 后台线程第6次执行!
1633 后台线程第7次执行!
1634 
1635 Process finished with exit code 0
1636 
1637  
1638 
1639 从上面的执行结果可以看出:
1640 
1641 前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
1642 
1643  
1644 
1645 实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。
1646 
1647 Java线程:线程的同步-同步方法
1648 线程的同步是保证多线程安全访问竞争资源的一种手段。
1649 
1650 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源、什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些原则问题需要考虑,是否有竞争资源被同时改动的问题?
1651 
1652  
1653 
1654 在本文之前,请参阅《Java线程:线程的同步与锁》,本文是在此基础上所写的。
1655 
1656  
1657 
1658 对于同步,在具体的Java代码中需要完成一下两个操作:
1659 
1660 把竞争访问的资源标识为private;
1661 
1662 同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
1663 
1664 当然这不是唯一控制并发安全的途径。
1665 
1666  
1667 
1668 synchronized关键字使用说明
1669 
1670 synchronized只能标记非抽象的方法,不能标识成员变量。
1671 
1672  
1673 
1674 为了演示同步方法的使用,构建了一个信用卡账户,起初信用额为100w,然后模拟透支、存款等多个操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户的余额设为私有变量,禁止直接访问。
1675 
1676  
1677 
1678  
1679 
1680 /** 
1681 * Java线程:线程的同步 
1682 * 
1683 * @author leizhimin 2009-11-4 11:23:32 
1684 */ 
1685 publicclass Test {
1686         publicstaticvoid main(String[] args) {
1687                 User u = new User("张三", 100);
1688                 MyThread t1 = new MyThread("线程A", u, 20);
1689                 MyThread t2 = new MyThread("线程B", u, -60);
1690                 MyThread t3 = new MyThread("线程C", u, -80);
1691                 MyThread t4 = new MyThread("线程D", u, -30);
1692                 MyThread t5 = new MyThread("线程E", u, 32);
1693                 MyThread t6 = new MyThread("线程F", u, 21);
1694 
1695                 t1.start(); 
1696                 t2.start(); 
1697                 t3.start(); 
1698                 t4.start(); 
1699                 t5.start(); 
1700                 t6.start(); 
1701         } 
1702 } 
1703 
1704 class MyThread extends Thread { 
1705         private User u;
1706         privateint y = 0;
1707 
1708         MyThread(String name, User u, int y) {
1709                 super(name);
1710                 this.u = u;
1711                 this.y = y;
1712         } 
1713 
1714         publicvoid run() {
1715                 u.oper(y); 
1716         } 
1717 } 
1718 
1719 class User { 
1720         private String code;
1721         privateint cash;
1722 
1723         User(String code, int cash) {
1724                 this.code = code;
1725                 this.cash = cash;
1726         } 
1727 
1728         public String getCode() {
1729                 return code;
1730         } 
1731 
1732         publicvoid setCode(String code) {
1733                 this.code = code;
1734         } 
1735 
1736         /** 
1737          * 业务方法
1738          * @param x 添加x万元
1739          */ 
1740         publicsynchronizedvoid oper(int x) {
1741                 try {
1742                         Thread.sleep(10L); 
1743                         this.cash += x;
1744                         System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x +"”,当前用户账户余额为:" + cash);
1745                         Thread.sleep(10L); 
1746                 } catch (InterruptedException e) {
1747                         e.printStackTrace(); 
1748                 } 
1749         } 
1750 
1751         @Override 
1752         public String toString() {
1753                 return"User{" +
1754                                 "code=‘" + code + \‘ +
1755                                 ", cash=" + cash +
1756                                 }; 
1757         } 
1758 }
1759 
1760  
1761 
1762 输出结果:
1763 
1764 线程A运行结束,增加“20”,当前用户账户余额为:120
1765 线程F运行结束,增加“21”,当前用户账户余额为:141
1766 线程E运行结束,增加“32”,当前用户账户余额为:173
1767 线程C运行结束,增加“-80”,当前用户账户余额为:93
1768 线程B运行结束,增加“-60”,当前用户账户余额为:33
1769 线程D运行结束,增加“-30”,当前用户账户余额为:3
1770 
1771 Process finished with exit code 0
1772 
1773  
1774 
1775  
1776 
1777 反面教材,不同步的情况,也就是去掉oper(int x)方法的synchronized修饰符,然后运行程序,结果如下:
1778 
1779 线程A运行结束,增加“20”,当前用户账户余额为:61
1780 线程D运行结束,增加“-30”,当前用户账户余额为:63
1781 线程B运行结束,增加“-60”,当前用户账户余额为:3
1782 线程F运行结束,增加“21”,当前用户账户余额为:61
1783 线程E运行结束,增加“32”,当前用户账户余额为:93
1784 线程C运行结束,增加“-80”,当前用户账户余额为:61
1785 
1786 Process finished with exit code 0
1787 
1788  
1789 
1790 很显然,上面的结果是错误的,导致错误的原因是多个线程并发访问了竞争资源u,并对u的属性做了改动。
1791 
1792  
1793 
1794 可见同步的重要性。
1795 
1796  
1797 
1798  
1799 
1800 注意:
1801 
1802 通过前文可知,线程退出同步方法时将释放掉方法所属对象的锁,但还应该注意的是,同步方法中还可以使用特定的方法对线程进行调度。这些方法来自于java.lang.Object类。
1803 
1804  
1805 
1806 void notify()    
1807                     唤醒在此对象监视器上等待的单个线程。    
1808 void notifyAll()    
1809                     唤醒在此对象监视器上等待的所有线程。    
1810 void wait()    
1811                     导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。    
1812 void wait(long timeout)    
1813                     导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。    
1814 void wait(long timeout,int nanos)    
1815                     导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
1816 
1817  
1818 
1819 结合以上方法,处理多线程同步与互斥问题非常重要,著名的生产者-消费者例子就是一个经典的例子,任何语言多线程必学的例子。
1820 
1821 Java线程:线程的同步-同步块
1822 对于同步,除了同步方法外,还可以使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。
1823 
1824  
1825 
1826 追其同步的根本的目的,是控制竞争资源的正确的访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,因此Java引入了同步代码快的策略,以提高性能。
1827 
1828  
1829 
1830 在上个例子的基础上,对oper方法做了改动,由同步方法改为同步代码块模式,程序的执行逻辑并没有问题。
1831 
1832  
1833 
1834  
1835 
1836 /** 
1837 * Java线程:线程的同步-同步代码块
1838 * 
1839 * @author leizhimin 2009-11-4 11:23:32 
1840 */ 
1841 publicclass Test {
1842         publicstaticvoid main(String[] args) {
1843                 User u = new User("张三", 100);
1844                 MyThread t1 = new MyThread("线程A", u, 20);
1845                 MyThread t2 = new MyThread("线程B", u, -60);
1846                 MyThread t3 = new MyThread("线程C", u, -80);
1847                 MyThread t4 = new MyThread("线程D", u, -30);
1848                 MyThread t5 = new MyThread("线程E", u, 32);
1849                 MyThread t6 = new MyThread("线程F", u, 21);
1850 
1851                 t1.start(); 
1852                 t2.start(); 
1853                 t3.start(); 
1854                 t4.start(); 
1855                 t5.start(); 
1856                 t6.start(); 
1857         } 
1858 } 
1859 
1860 class MyThread extends Thread { 
1861         private User u;
1862         privateint y = 0;
1863 
1864         MyThread(String name, User u, int y) {
1865                 super(name);
1866                 this.u = u;
1867                 this.y = y;
1868         } 
1869 
1870         publicvoid run() {
1871                 u.oper(y); 
1872         } 
1873 } 
1874 
1875 class User { 
1876         private String code;
1877         privateint cash;
1878 
1879         User(String code, int cash) {
1880                 this.code = code;
1881                 this.cash = cash;
1882         } 
1883 
1884         public String getCode() {
1885                 return code;
1886         } 
1887 
1888         publicvoid setCode(String code) {
1889                 this.code = code;
1890         } 
1891 
1892         /** 
1893          * 业务方法
1894          * 
1895          * @param x 添加x万元
1896          */ 
1897         publicvoid oper(int x) {
1898                 try {
1899                         Thread.sleep(10L); 
1900                         synchronized (this) {
1901                                 this.cash += x;
1902                                 System.out.println(Thread.currentThread().getName() +"运行结束,增加“" + x +"”,当前用户账户余额为:" + cash);
1903                         } 
1904                         Thread.sleep(10L); 
1905                 } catch (InterruptedException e) {
1906                         e.printStackTrace(); 
1907                 } 
1908         } 
1909 
1910         @Override 
1911         public String toString() {
1912                 return"User{" +
1913                                 "code=‘" + code + \‘ +
1914                                 ", cash=" + cash +
1915                                 }; 
1916         } 
1917 }
1918 
1919  
1920 
1921 线程E运行结束,增加“32”,当前用户账户余额为:132
1922 线程B运行结束,增加“-60”,当前用户账户余额为:72
1923 线程D运行结束,增加“-30”,当前用户账户余额为:42
1924 线程F运行结束,增加“21”,当前用户账户余额为:63
1925 线程C运行结束,增加“-80”,当前用户账户余额为:-17
1926 线程A运行结束,增加“20”,当前用户账户余额为:3
1927 
1928 Process finished with exit code 0
1929 
1930  
1931 
1932 注意:
1933 
1934 在使用synchronized关键字时候,应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法,因为synchronized程序块占有着对象锁,你休息那么其他的线程只能一边等着你醒来执行完了才能执行。不但严重影响效率,也不合逻辑。
1935 
1936 同样,在同步程序块内调用yeild方法让出CPU资源也没有意义,因为你占用着锁,其他互斥线程还是无法访问同步程序块。当然与同步程序块无关的线程可以获得更多的执行时间。
1937 
1938 Java线程:并发协作-生产者消费者模型
1939 对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的。就像学习每一门编程语言一样,Hello World!都是最经典的例子。
1940 
1941  
1942 
1943 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓储,生产者消费者模型就显得没有说服力了。
1944 
1945 对于此模型,应该明确一下几点:
1946 
1947 1、生产者仅仅在仓储未满时候生产,仓满则停止生产。
1948 
1949 2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。
1950 
1951 3、当消费者发现仓储没产品可消费时候会通知生产者生产。
1952 
1953 4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
1954 
1955  
1956 
1957 此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。这是非常重要的。
1958 
1959  
1960 
1961 /** 
1962 * Java线程:并发协作-生产者消费者模型
1963 * 
1964 * @author leizhimin 2009-11-4 14:54:36 
1965 */ 
1966 publicclass Test {
1967         publicstaticvoid main(String[] args) {
1968                 Godown godown = new Godown(30);
1969                 Consumer c1 = new Consumer(50, godown);
1970                 Consumer c2 = new Consumer(20, godown);
1971                 Consumer c3 = new Consumer(30, godown);
1972                 Producer p1 = new Producer(10, godown);
1973                 Producer p2 = new Producer(10, godown);
1974                 Producer p3 = new Producer(10, godown);
1975                 Producer p4 = new Producer(10, godown);
1976                 Producer p5 = new Producer(10, godown);
1977                 Producer p6 = new Producer(10, godown);
1978                 Producer p7 = new Producer(80, godown);
1979 
1980                 c1.start(); 
1981                 c2.start(); 
1982                 c3.start(); 
1983                 p1.start(); 
1984                 p2.start(); 
1985                 p3.start(); 
1986                 p4.start(); 
1987                 p5.start(); 
1988                 p6.start(); 
1989                 p7.start(); 
1990         } 
1991 } 
1992 
1993 /** 
1994 * 仓库 
1995 */ 
1996 class Godown { 
1997         publicstaticfinalint max_size = 100;//最大库存量
1998         publicint curnum;    //当前库存量
1999 
2000         Godown() { 
2001         } 
2002 
2003         Godown(int curnum) {
2004                 this.curnum = curnum;
2005         } 
2006 
2007         /** 
2008          * 生产指定数量的产品
2009          * 
2010          * @param neednum 
2011          */ 
2012         publicsynchronizedvoid produce(int neednum) {
2013                 //测试是否需要生产
2014                 while (neednum + curnum > max_size) {
2015                         System.out.println("要生产的产品数量" + neednum +"超过剩余库存量" + (max_size - curnum) +",暂时不能执行生产任务!");
2016                         try {
2017                                 //当前的生产线程等待
2018                                 wait(); 
2019                         } catch (InterruptedException e) {
2020                                 e.printStackTrace(); 
2021                         } 
2022                 } 
2023                 //满足生产条件,则进行生产,这里简单的更改当前库存量
2024                 curnum += neednum; 
2025                 System.out.println("已经生产了" + neednum +"个产品,现仓储量为" + curnum);
2026                 //唤醒在此对象监视器上等待的所有线程
2027                 notifyAll(); 
2028         } 
2029 
2030         /** 
2031          * 消费指定数量的产品
2032          * 
2033          * @param neednum 
2034          */ 
2035         publicsynchronizedvoid consume(int neednum) {
2036                 //测试是否可消费
2037                 while (curnum < neednum) {
2038                         try {
2039                                 //当前的生产线程等待
2040                                 wait(); 
2041                         } catch (InterruptedException e) {
2042                                 e.printStackTrace(); 
2043                         } 
2044                 } 
2045                 //满足消费条件,则进行消费,这里简单的更改当前库存量
2046                 curnum -= neednum; 
2047                 System.out.println("已经消费了" + neednum +"个产品,现仓储量为" + curnum);
2048                 //唤醒在此对象监视器上等待的所有线程
2049                 notifyAll(); 
2050         } 
2051 } 
2052 
2053 /** 
2054 * 生产者 
2055 */ 
2056 class Producer extends Thread { 
2057         privateint neednum;                //生产产品的数量
2058         private Godown godown;            //仓库
2059 
2060         Producer(int neednum, Godown godown) {
2061                 this.neednum = neednum;
2062                 this.godown = godown;
2063         } 
2064 
2065         publicvoid run() {
2066                 //生产指定数量的产品
2067                 godown.produce(neednum); 
2068         } 
2069 } 
2070 
2071 /** 
2072 * 消费者 
2073 */ 
2074 class Consumer extends Thread { 
2075         privateint neednum;                //生产产品的数量
2076         private Godown godown;            //仓库
2077 
2078         Consumer(int neednum, Godown godown) {
2079                 this.neednum = neednum;
2080                 this.godown = godown;
2081         } 
2082 
2083         publicvoid run() {
2084                 //消费指定数量的产品
2085                 godown.consume(neednum); 
2086         } 
2087 }
2088 
2089  
2090 
2091 已经生产了10个产品,现仓储量为40
2092 已经生产了10个产品,现仓储量为50
2093 已经消费了50个产品,现仓储量为0
2094 已经生产了80个产品,现仓储量为80
2095 已经消费了30个产品,现仓储量为50
2096 已经生产了10个产品,现仓储量为60
2097 已经消费了20个产品,现仓储量为40
2098 已经生产了10个产品,现仓储量为50
2099 已经生产了10个产品,现仓储量为60
2100 已经生产了10个产品,现仓储量为70
2101 
2102 Process finished with exit code 0
2103 
2104  
2105 
2106 说明:
2107 
2108 对于本例,要说明的是当发现不能满足生产或者消费条件的时候,调用对象的wait方法,wait方法的作用是释放当前线程的所获得的锁,并调用对象的notifyAll()方法,通知(唤醒)该对象上其他等待线程,使得其继续执行。这样,整个生产者、消费者线程得以正确的协作执行。
2109 
2110 notifyAll() 方法,起到的是一个通知作用,不释放锁,也不获取锁。只是告诉该对象上等待的线程“可以竞争执行了,都醒来去执行吧”。
2111 
2112  
2113 
2114 本例仅仅是生产者消费者模型中最简单的一种表示,本例中,如果消费者消费的仓储量达不到满足,而又没有生产者,则程序会一直处于等待状态,这当然是不对的。实际上可以将此例进行修改,修改为,根据消费驱动生产,同时生产兼顾仓库,如果仓不满就生产,并对每次最大消费量做个限制,这样就不存在此问题了,当然这样的例子更复杂,更难以说明这样一个简单模型。
2115 
2116  
2117 
2118 我喜欢简单的例子。
2119 
2120 Java线程:并发协作-死锁
2121 线程发生死锁可能性很小,即使看似可能发生死锁的代码,在运行时发生死锁的可能性也是小之又小。
2122 
2123  
2124 
2125 发生死锁的原因一般是两个对象的锁相互等待造成的。
2126 
2127  
2128 
2129 在《Java线程:线程的同步与锁》一文中,简述死锁的概念与简单例子,但是所给的例子是不完整的,这里给出一个完整的例子。
2130 
2131  
2132 
2133 /** 
2134 * Java线程:并发协作-死锁
2135 * 
2136 * @author Administrator 2009-11-4 22:06:13 
2137 */ 
2138 publicclass Test {
2139         publicstaticvoid main(String[] args) {
2140                 DeadlockRisk dead = new DeadlockRisk();
2141                 MyThread t1 = new MyThread(dead, 1, 2);
2142                 MyThread t2 = new MyThread(dead, 3, 4);
2143                 MyThread t3 = new MyThread(dead, 5, 6);
2144                 MyThread t4 = new MyThread(dead, 7, 8);
2145 
2146                 t1.start(); 
2147                 t2.start(); 
2148                 t3.start(); 
2149                 t4.start(); 
2150         } 
2151 
2152 } 
2153 
2154 class MyThread extends Thread { 
2155         private DeadlockRisk dead;
2156         privateint a, b;
2157 
2158 
2159         MyThread(DeadlockRisk dead, int a,int b) { 
2160                 this.dead = dead;
2161                 this.a = a;
2162                 this.b = b;
2163         } 
2164 
2165         @Override 
2166         publicvoid run() {
2167                 dead.read(); 
2168                 dead.write(a, b); 
2169         } 
2170 } 
2171 
2172 class DeadlockRisk {
2173         privatestaticclass Resource {
2174                 publicint value;
2175         } 
2176 
2177         private Resource resourceA =new Resource(); 
2178         private Resource resourceB =new Resource(); 
2179 
2180         publicint read() {
2181                 synchronized (resourceA) {
2182                         System.out.println("read():" + Thread.currentThread().getName() +"获取了resourceA的锁!");
2183                         synchronized (resourceB) {
2184                                 System.out.println("read():" + Thread.currentThread().getName() +"获取了resourceB的锁!");
2185                                 return resourceB.value + resourceA.value;
2186                         } 
2187                 } 
2188         } 
2189 
2190         publicvoid write(int a,int b) { 
2191                 synchronized (resourceB) {
2192                         System.out.println("write():" + Thread.currentThread().getName() +"获取了resourceA的锁!");
2193                         synchronized (resourceA) {
2194                                 System.out.println("write():" + Thread.currentThread().getName() +"获取了resourceB的锁!");
2195                                 resourceA.value = a; 
2196                                 resourceB.value = b; 
2197                         } 
2198                 } 
2199         } 
2200 }
2201 
2202  
2203 
2204 下面死锁的情况发生了,真是难得一见啊:
2205 
2206  
2207 
2208  
2209 
2210 Java线程:volatile关键字
2211 
2212  
2213 
2214 Java? 语言包含两种内在的同步机制:同步块(或方法)和 volatile变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。
2215 
2216  
2217 
2218 谈及到volatile关键字,不得不提的一篇文章是:《Java理论与实践:正确使用 Volatile 变量》,这篇文章对volatile关键字的用法做了相当精辟的阐述。
2219 
2220  
2221 
2222 之所以要单独提出volatile这个不常用的关键字原因是这个关键字在高性能的多线程程序中也有很重要的用途,只是这个关键字用不好会出很多问题。
2223 
2224  
2225 
2226 首先考虑一个问题,为什么变量需要volatile来修饰呢?
2227 
2228 要搞清楚这个问题,首先应该明白计算机内部都做什么了。比如做了一个i++操作,计算机内部做了三次处理:读取-修改-写入。
2229 
2230 同样,对于一个long型数据,做了个赋值操作,在32系统下需要经过两步才能完成,先修改低32位,然后修改高32位。
2231 
2232  
2233 
2234 假想一下,当将以上的操作放到一个多线程环境下操作时候,有可能出现的问题,是这些步骤执行了一部分,而另外一个线程就已经引用了变量值,这样就导致了读取脏数据的问题。
2235 
2236  
2237 
2238 通过这个设想,就不难理解volatile关键字了。
2239 
2240  
2241 
2242 volatile可以用在任何变量前面,但不能用于final变量前面,因为final型的变量是禁止修改的。也不存在线程安全的问题。
2243 
2244  
2245 
2246 更多的内容,请参看::《Java理论与实践:正确使用 Volatile 变量》一文,写得很好。
2247 
2248 Java线程:新特征-线程池
2249 Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利。为了编写高效稳定可靠的多线程程序,线程部分的新增内容显得尤为重要。
2250 
2251  
2252 
2253 有关Java5线程新特征的内容全部在java.util.concurrent下面,里面包含数目众多的接口和类,熟悉这部分API特征是一项艰难的学习过程。目前有关这方面的资料和书籍都少之又少,大所属介绍线程方面书籍还停留在java5之前的知识层面上。
2254 
2255  
2256 
2257 当然新特征对做多线程程序没有必须的关系,在java5之前通用可以写出很优秀的多线程程序。只是代价不一样而已。
2258 
2259  
2260 
2261 线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
2262 
2263  
2264 
2265 在Java5之前,要实现一个线程池是相当有难度的,现在Java5为我们做好了一切,我们只需要按照提供的API来使用,即可享受线程池带来的极大便利。
2266 
2267  
2268 
2269 Java5的线程池分好多种:固定尺寸的线程池、可变尺寸连接池、。
2270 
2271  
2272 
2273 在使用线程池之前,必须知道如何去创建一个线程池,在Java5中,需要了解的是java.util.concurrent.Executors类的API,这个类提供大量创建连接池的静态方法,是必须掌握的。
2274 
2275  
2276 
2277 一、固定大小的线程池
2278 
2279  
2280 
2281 import java.util.concurrent.Executors;
2282 import java.util.concurrent.ExecutorService;
2283 
2284 /** 
2285 * Java线程:线程池- 
2286 * 
2287 * @author Administrator 2009-11-4 23:30:44 
2288 */ 
2289 publicclass Test {
2290         publicstaticvoid main(String[] args) {
2291                 //创建一个可重用固定线程数的线程池
2292                 ExecutorService pool = Executors.newFixedThreadPool(2); 
2293                 //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
2294                 Thread t1 = new MyThread();
2295                 Thread t2 = new MyThread();
2296                 Thread t3 = new MyThread();
2297                 Thread t4 = new MyThread();
2298                 Thread t5 = new MyThread();
2299                 //将线程放入池中进行执行
2300                 pool.execute(t1); 
2301                 pool.execute(t2); 
2302                 pool.execute(t3); 
2303                 pool.execute(t4); 
2304                 pool.execute(t5); 
2305                 //关闭线程池
2306                 pool.shutdown(); 
2307         } 
2308 } 
2309 
2310 class MyThread extends Thread{ 
2311         @Override 
2312         publicvoid run() {
2313                 System.out.println(Thread.currentThread().getName()+"正在执行。。。");
2314         } 
2315 }
2316 
2317  
2318 
2319 pool-1-thread-1正在执行。。。
2320 pool-1-thread-1正在执行。。。
2321 pool-1-thread-1正在执行。。。
2322 pool-1-thread-1正在执行。。。
2323 pool-1-thread-2正在执行。。。
2324 
2325 Process finished with exit code 0
2326 
2327  
2328 
2329 二、单任务线程池
2330 
2331  
2332 
2333 在上例的基础上改一行创建pool对象的代码为:
2334 
2335                 //创建一个使用单个 worker线程的 Executor,以无界队列方式来运行该线程。
2336                 ExecutorService pool = Executors.newSingleThreadExecutor();
2337 
2338  
2339 
2340 输出结果为:
2341 
2342 pool-1-thread-1正在执行。。。
2343 pool-1-thread-1正在执行。。。
2344 pool-1-thread-1正在执行。。。
2345 pool-1-thread-1正在执行。。。
2346 pool-1-thread-1正在执行。。。
2347 
2348 Process finished with exit code 0
2349 
2350  
2351 
2352 对于以上两种连接池,大小都是固定的,当要加入的池的线程(或者任务)超过池最大尺寸时候,则入此线程池需要排队等待。
2353 
2354 一旦池中有线程完毕,则排队等待的某个线程会入池执行。
2355 
2356  
2357 
2358 三、可变尺寸的线程池
2359 
2360  
2361 
2362 与上面的类似,只是改动下pool的创建方式:
2363 
2364                 //创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
2365                 ExecutorService pool = Executors.newCachedThreadPool();
2366 
2367  
2368 
2369 pool-1-thread-5正在执行。。。
2370 pool-1-thread-1正在执行。。。
2371 pool-1-thread-4正在执行。。。
2372 pool-1-thread-3正在执行。。。
2373 pool-1-thread-2正在执行。。。
2374 
2375 Process finished with exit code 0
2376 
2377  
2378 
2379 四、延迟连接池
2380 
2381  
2382 
2383 import java.util.concurrent.Executors;
2384 import java.util.concurrent.ScheduledExecutorService;
2385 import java.util.concurrent.TimeUnit;
2386 
2387 /** 
2388 * Java线程:线程池- 
2389 * 
2390 * @author Administrator 2009-11-4 23:30:44 
2391 */ 
2392 publicclass Test {
2393         publicstaticvoid main(String[] args) {
2394                 //创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
2395                 ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
2396                 //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
2397                 Thread t1 = new MyThread();
2398                 Thread t2 = new MyThread();
2399                 Thread t3 = new MyThread();
2400                 Thread t4 = new MyThread();
2401                 Thread t5 = new MyThread();
2402                 //将线程放入池中进行执行
2403                 pool.execute(t1); 
2404                 pool.execute(t2); 
2405                 pool.execute(t3); 
2406                 //使用延迟执行风格的方法
2407                 pool.schedule(t4, 10, TimeUnit.MILLISECONDS); 
2408                 pool.schedule(t5, 10, TimeUnit.MILLISECONDS); 
2409                 //关闭线程池
2410                 pool.shutdown(); 
2411         } 
2412 } 
2413 
2414 class MyThread extends Thread { 
2415         @Override 
2416         publicvoid run() {
2417                 System.out.println(Thread.currentThread().getName() + "正在执行。。。");
2418         } 
2419 }
2420 
2421  
2422 
2423 pool-1-thread-1正在执行。。。
2424 pool-1-thread-2正在执行。。。
2425 pool-1-thread-1正在执行。。。
2426 pool-1-thread-1正在执行。。。
2427 pool-1-thread-2正在执行。。。
2428 
2429 Process finished with exit code 0
2430 
2431  
2432 
2433 五、单任务延迟连接池
2434 
2435  
2436 
2437 在四代码基础上,做改动
2438 
2439                 //创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
2440                 ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
2441 
2442  
2443 
2444 pool-1-thread-1正在执行。。。
2445 pool-1-thread-1正在执行。。。
2446 pool-1-thread-1正在执行。。。
2447 pool-1-thread-1正在执行。。。
2448 pool-1-thread-1正在执行。。。
2449 
2450 Process finished with exit code 0
2451 
2452  
2453 
2454 六、自定义线程池
2455 
2456  
2457 
2458 import java.util.concurrent.ArrayBlockingQueue;
2459 import java.util.concurrent.BlockingQueue;
2460 import java.util.concurrent.ThreadPoolExecutor;
2461 import java.util.concurrent.TimeUnit;
2462 
2463 /** 
2464 * Java线程:线程池-自定义线程池
2465 * 
2466 * @author Administrator 2009-11-4 23:30:44 
2467 */ 
2468 publicclass Test {
2469         publicstaticvoid main(String[] args) {
2470                 //创建等待队列
2471                 BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);
2472                 //创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
2473                 ThreadPoolExecutor pool = new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue);
2474                 //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
2475                 Thread t1 = new MyThread();
2476                 Thread t2 = new MyThread();
2477                 Thread t3 = new MyThread();
2478                 Thread t4 = new MyThread();
2479                 Thread t5 = new MyThread();
2480                 Thread t6 = new MyThread();
2481                 Thread t7 = new MyThread();
2482                 //将线程放入池中进行执行
2483                 pool.execute(t1); 
2484                 pool.execute(t2); 
2485                 pool.execute(t3); 
2486                 pool.execute(t4); 
2487                 pool.execute(t5); 
2488                 pool.execute(t6); 
2489                 pool.execute(t7); 
2490                 //关闭线程池
2491                 pool.shutdown(); 
2492         } 
2493 } 
2494 
2495 class MyThread extends Thread { 
2496         @Override 
2497         publicvoid run() {
2498                 System.out.println(Thread.currentThread().getName() + "正在执行。。。");
2499                 try {
2500                         Thread.sleep(100L); 
2501                 } catch (InterruptedException e) {
2502                         e.printStackTrace(); 
2503                 } 
2504         } 
2505 }
2506 
2507  
2508 
2509 pool-1-thread-1正在执行。。。
2510 pool-1-thread-2正在执行。。。
2511 pool-1-thread-2正在执行。。。
2512 pool-1-thread-1正在执行。。。
2513 pool-1-thread-2正在执行。。。
2514 pool-1-thread-1正在执行。。。
2515 pool-1-thread-2正在执行。。。
2516 
2517 Process finished with exit code 0
2518 
2519  
2520 
2521 创建自定义线程池的构造方法很多,本例中参数的含义如下:
2522 
2523 ThreadPoolExecutor
2524 
2525 public ThreadPoolExecutor(int corePoolSize,
2526 
2527                          int maximumPoolSize,
2528 
2529                          long keepAliveTime,
2530 
2531                           TimeUnit unit,
2532 
2533                          BlockingQueue<Runnable> workQueue)
2534 
2535 用给定的初始参数和默认的线程工厂及处理程序创建新的ThreadPoolExecutor。使用Executors工厂方法之一比使用此通用构造方法方便得多。
2536 
2537 参数:
2538 
2539 corePoolSize -池中所保存的线程数,包括空闲线程。
2540 
2541 maximumPoolSize -池中允许的最大线程数。
2542 
2543 keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
2544 
2545 unit - keepAliveTime参数的时间单位。
2546 
2547 workQueue -执行前用于保持任务的队列。此队列仅保持由execute方法提交的Runnable任务。
2548 
2549 抛出:
2550 
2551 IllegalArgumentException -如果 corePoolSize或 keepAliveTime小于零,或者 maximumPoolSize小于或等于零,或者 corePoolSize大于 maximumPoolSize。
2552 
2553 NullPointerException -如果workQueue为 null
2554 
2555  
2556 
2557 自定义连接池稍微麻烦些,不过通过创建的ThreadPoolExecutor线程池对象,可以获取到当前线程池的尺寸、正在执行任务的线程数、工作队列等等。
2558 
2559  
2560 
2561 有关Java5线程池的内容到此就没有了,更多的内容还需要研读API来获取。
2562 
2563 Java线程:新特征-有返回值的线程
2564 在Java5之前,线程是没有返回值的,常常为了“有”返回值,破费周折,而且代码很不好写。或者干脆绕过这道坎,走别的路了。
2565 
2566  
2567 
2568 现在Java终于有可返回值的任务(也可以叫做线程)了。
2569 
2570  
2571 
2572 可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。
2573 
2574  
2575 
2576 执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
2577 
2578  
2579 
2580 下面是个很简单的例子:
2581 
2582  
2583 
2584 import java.util.concurrent.*;
2585 
2586 /** 
2587 * Java线程:有返回值的线程
2588 * 
2589 * @author Administrator 2009-11-5 0:41:50 
2590 */ 
2591 publicclass Test {
2592         publicstaticvoid main(String[] args)throws ExecutionException, InterruptedException {
2593                 //创建一个线程池
2594                 ExecutorService pool = Executors.newFixedThreadPool(2); 
2595                 //创建两个有返回值的任务
2596                 Callable c1 = new MyCallable("A");
2597                 Callable c2 = new MyCallable("B");
2598                 //执行任务并获取Future对象
2599                 Future f1 = pool.submit(c1); 
2600                 Future f2 = pool.submit(c2); 
2601                 //从Future对象上获取任务的返回值,并输出到控制台
2602                 System.out.println(">>>"+f1.get().toString());
2603                 System.out.println(">>>"+f2.get().toString());
2604                 //关闭线程池
2605                 pool.shutdown(); 
2606         } 
2607 } 
2608 
2609 class MyCallableimplements Callable{
2610         private String oid;
2611 
2612         MyCallable(String oid) { 
2613                 this.oid = oid;
2614         } 
2615 
2616         @Override 
2617         public Object call()throws Exception {
2618                 return oid+"任务返回的内容";
2619         } 
2620 }
2621 
2622  
2623 
2624 >>>A任务返回的内容
2625 >>>B任务返回的内容 
2626 
2627 Process finished with exit code 0
2628 
2629  
2630 
2631 非常的简单,要深入了解还需要看Callable和Future接口的API啊。
2632 
2633 Java线程:新特征-锁(上)
2634 在Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.locks包下面,里面有三个重要的接口Condition、Lock、ReadWriteLock。
2635 
2636  
2637 
2638 Condition
2639 
2640 Condition将Object监视器方法(wait、notify和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set)。
2641 
2642 Lock
2643 
2644 Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
2645 
2646 ReadWriteLock
2647 
2648 ReadWriteLock维护了一对相关的锁定,一个用于只读操作,另一个用于写入操作。
2649 
2650  
2651 
2652 有关锁的介绍,API文档解说很多,看得很烦,还是看个例子再看文档比较容易理解。
2653 
2654  
2655 
2656 import java.util.concurrent.ExecutorService;
2657 import java.util.concurrent.Executors;
2658 import java.util.concurrent.locks.Lock;
2659 import java.util.concurrent.locks.ReentrantLock;
2660 
2661 /** 
2662 * Java线程:锁 
2663 * 
2664 * @author leizhimin 2009-11-5 10:57:29 
2665 */ 
2666 publicclass Test {
2667         publicstaticvoid main(String[] args) {
2668                 //创建并发访问的账户
2669                 MyCount myCount = new MyCount("95599200901215522", 10000);
2670                 //创建一个锁对象
2671                 Lock lock = new ReentrantLock();
2672                 //创建一个线程池
2673                 ExecutorService pool = Executors.newCachedThreadPool(); 
2674                 //创建一些并发访问用户,一个信用卡,存的存,取的取,好热闹啊
2675                 User u1 = new User("张三", myCount, -4000, lock); 
2676                 User u2 = new User("张三他爹", myCount, 6000, lock); 
2677                 User u3 = new User("张三他弟", myCount, -8000, lock); 
2678                 User u4 = new User("张三", myCount, 800, lock);
2679                 //在线程池中执行各个用户的操作
2680                 pool.execute(u1); 
2681                 pool.execute(u2); 
2682                 pool.execute(u3); 
2683                 pool.execute(u4); 
2684                 //关闭线程池
2685                 pool.shutdown(); 
2686         } 
2687 } 
2688 
2689 /** 
2690 * 信用卡的用户 
2691 */ 
2692 class User implements Runnable { 
2693         private String name;                //用户名
2694         private MyCount myCount;        //所要操作的账户
2695         privateint iocash;                //操作的金额,当然有正负之分了
2696         private Lock myLock;                //执行操作所需的锁对象
2697 
2698         User(String name, MyCount myCount, int iocash, Lock myLock) {
2699                 this.name = name;
2700                 this.myCount = myCount;
2701                 this.iocash = iocash;
2702                 this.myLock = myLock;
2703         } 
2704 
2705         publicvoid run() {
2706                 //获取锁
2707                 myLock.lock(); 
2708                 //执行现金业务
2709                 System.out.println(name + "正在操作" + myCount +"账户,金额为" + iocash +",当前金额为" + myCount.getCash());
2710                 myCount.setCash(myCount.getCash() + iocash); 
2711                 System.out.println(name + "操作" + myCount +"账户成功,金额为" + iocash +",当前金额为" + myCount.getCash());
2712                 //释放锁,否则别的线程没有机会执行了
2713                 myLock.unlock(); 
2714         } 
2715 } 
2716 
2717 /** 
2718 * 信用卡账户,可随意透支 
2719 */ 
2720 class MyCount { 
2721         private String oid;        //账号
2722         privateint cash;            //账户余额
2723 
2724         MyCount(String oid, int cash) {
2725                 this.oid = oid;
2726                 this.cash = cash;
2727         } 
2728 
2729         public String getOid() {
2730                 return oid;
2731         } 
2732 
2733         publicvoid setOid(String oid) {
2734                 this.oid = oid;
2735         } 
2736 
2737         publicint getCash() {
2738                 return cash;
2739         } 
2740 
2741         publicvoid setCash(int cash) {
2742                 this.cash = cash;
2743         } 
2744 
2745         @Override 
2746         public String toString() {
2747                 return"MyCount{" +
2748                                 "oid=‘" + oid + \‘ +
2749                                 ", cash=" + cash +
2750                                 }; 
2751         } 
2752 }
2753 
2754  
2755 
2756 张三正在操作MyCount{oid=95599200901215522, cash=10000}账户,金额为-4000,当前金额为10000
2757 张三操作MyCount{oid=95599200901215522, cash=6000}账户成功,金额为-4000,当前金额为6000
2758 张三他爹正在操作MyCount{oid=95599200901215522, cash=6000}账户,金额为6000,当前金额为6000
2759 张三他爹操作MyCount{oid=95599200901215522, cash=12000}账户成功,金额为6000,当前金额为12000
2760 张三他弟正在操作MyCount{oid=95599200901215522, cash=12000}账户,金额为-8000,当前金额为12000
2761 张三他弟操作MyCount{oid=95599200901215522, cash=4000}账户成功,金额为-8000,当前金额为4000
2762 张三正在操作MyCount{oid=95599200901215522, cash=4000}账户,金额为800,当前金额为4000
2763 张三操作MyCount{oid=95599200901215522, cash=4800}账户成功,金额为800,当前金额为4800
2764 
2765 Process finished with exit code 0
2766 
2767  
2768 
2769 从上面的输出可以看到,利用锁对象太方便了,比直接在某个不知情的对象上用锁清晰多了。
2770 
2771  
2772 
2773 但一定要注意的是,在获取了锁对象后,用完后应该尽快释放锁,以便别的等待该锁的线程有机会去执行。
2774 
2775 Java线程:新特征-锁(下)
2776 在上文中提到了Lock接口以及对象,使用它,很优雅的控制了竞争资源的安全访问,但是这种锁不区分读写,称这种锁为普通锁。为了提高性能,Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,在一定程度上提高了程序的执行效率。
2777 
2778  
2779 
2780 Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock,也有具体的实现ReentrantReadWriteLock,详细的API可以查看JavaAPI文档。
2781 
2782  
2783 
2784 下面这个例子是在文例子的基础上,将普通锁改为读写锁,并添加账户余额查询的功能,代码如下:
2785 
2786  
2787 
2788 import java.util.concurrent.ExecutorService;
2789 import java.util.concurrent.Executors;
2790 import java.util.concurrent.locks.ReadWriteLock;
2791 import java.util.concurrent.locks.ReentrantReadWriteLock;
2792 
2793 /** 
2794 * Java线程:锁 
2795 * 
2796 * @author leizhimin 2009-11-5 10:57:29 
2797 */ 
2798 publicclass Test {
2799         publicstaticvoid main(String[] args) {
2800                 //创建并发访问的账户
2801                 MyCount myCount = new MyCount("95599200901215522", 10000);
2802                 //创建一个锁对象
2803                 ReadWriteLock lock = new ReentrantReadWriteLock(false);
2804                 //创建一个线程池
2805                 ExecutorService pool = Executors.newFixedThreadPool(2); 
2806                 //创建一些并发访问用户,一个信用卡,存的存,取的取,好热闹啊
2807                 User u1 = new User("张三", myCount, -4000, lock, false); 
2808                 User u2 = new User("张三他爹", myCount, 6000, lock, false); 
2809                 User u3 = new User("张三他弟", myCount, -8000, lock, false); 
2810                 User u4 = new User("张三", myCount, 800, lock,false); 
2811                 User u5 = new User("张三他爹", myCount, 0, lock,true); 
2812                 //在线程池中执行各个用户的操作
2813                 pool.execute(u1); 
2814                 pool.execute(u2); 
2815                 pool.execute(u3); 
2816                 pool.execute(u4); 
2817                 pool.execute(u5); 
2818                 //关闭线程池
2819                 pool.shutdown(); 
2820         } 
2821 } 
2822 
2823 /** 
2824 * 信用卡的用户 
2825 */ 
2826 class User implements Runnable { 
2827         private String name;                //用户名
2828         private MyCount myCount;        //所要操作的账户
2829         privateint iocash;                //操作的金额,当然有正负之分了
2830         private ReadWriteLock myLock;                //执行操作所需的锁对象
2831         privateboolean ischeck;        //是否查询
2832 
2833         User(String name, MyCount myCount, int iocash, ReadWriteLock myLock,boolean ischeck) {
2834                 this.name = name;
2835                 this.myCount = myCount;
2836                 this.iocash = iocash;
2837                 this.myLock = myLock;
2838                 this.ischeck = ischeck;
2839         } 
2840 
2841         publicvoid run() {
2842                 if (ischeck) {
2843                         //获取读锁
2844                         myLock.readLock().lock(); 
2845                         System.out.println("读:" + name +"正在查询" + myCount +"账户,当前金额为" + myCount.getCash());
2846                         //释放读锁
2847                         myLock.readLock().unlock(); 
2848                 } else {
2849                         //获取写锁
2850                         myLock.writeLock().lock(); 
2851                         //执行现金业务
2852                         System.out.println("写:" + name +"正在操作" + myCount +"账户,金额为" + iocash +",当前金额为" + myCount.getCash());
2853                         myCount.setCash(myCount.getCash() + iocash); 
2854                         System.out.println("写:" + name +"操作" + myCount +"账户成功,金额为" + iocash +",当前金额为" + myCount.getCash());
2855                         //释放写锁
2856                         myLock.writeLock().unlock(); 
2857                 } 
2858         } 
2859 } 
2860 
2861 /** 
2862 * 信用卡账户,可随意透支 
2863 */ 
2864 class MyCount { 
2865         private String oid;        //账号
2866         privateint cash;            //账户余额
2867 
2868         MyCount(String oid, int cash) {
2869                 this.oid = oid;
2870                 this.cash = cash;
2871         } 
2872 
2873         public String getOid() {
2874                 return oid;
2875         } 
2876 
2877         publicvoid setOid(String oid) {
2878                 this.oid = oid;
2879         } 
2880 
2881         publicint getCash() {
2882                 return cash;
2883         } 
2884 
2885         publicvoid setCash(int cash) {
2886                 this.cash = cash;
2887         } 
2888 
2889         @Override 
2890         public String toString() {
2891                 return"MyCount{" +
2892                                 "oid=‘" + oid + \‘ +
2893                                 ", cash=" + cash +
2894                                 }; 
2895         } 
2896 }
2897 
2898  
2899 
2900 写:张三正在操作MyCount{oid=95599200901215522, cash=10000}账户,金额为-4000,当前金额为10000
2901 写:张三操作MyCount{oid=95599200901215522, cash=6000}账户成功,金额为-4000,当前金额为6000
2902 写:张三他弟正在操作MyCount{oid=95599200901215522, cash=6000}账户,金额为-8000,当前金额为6000
2903 写:张三他弟操作MyCount{oid=95599200901215522, cash=-2000}账户成功,金额为-8000,当前金额为-2000
2904 写:张三正在操作MyCount{oid=95599200901215522, cash=-2000}账户,金额为800,当前金额为-2000
2905 写:张三操作MyCount{oid=95599200901215522, cash=-1200}账户成功,金额为800,当前金额为-1200
2906 读:张三他爹正在查询MyCount{oid=95599200901215522, cash=-1200}账户,当前金额为-1200
2907 写:张三他爹正在操作MyCount{oid=95599200901215522, cash=-1200}账户,金额为6000,当前金额为-1200
2908 写:张三他爹操作MyCount{oid=95599200901215522, cash=4800}账户成功,金额为6000,当前金额为4800
2909 
2910 Process finished with exit code 0
2911 
2912  
2913 
2914 在实际开发中,最好在能用读写锁的情况下使用读写锁,而不要用普通锁,以求更好的性能。
2915 
2916 Java线程:新特征-信号量
2917 Java的信号量实际上是一个功能完毕的计数器,对控制一定资源的消费与回收有着很重要的意义,信号量常常用于多线程的代码中,并能监控有多少数目的线程等待获取资源,并且通过信号量可以得知可用资源的数目等等,这里总是在强调“数目”二字,但不能指出来有哪些在等待,哪些资源可用。
2918 
2919  
2920 
2921 因此,本人认为,这个信号量类如果能返回数目,还能知道哪些对象在等待,哪些资源可使用,就非常完美了,仅仅拿到这些概括性的数字,对精确控制意义不是很大。目前还没想到更好的用法。
2922 
2923  
2924 
2925 下面是一个简单例子:
2926 
2927  
2928 
2929 import java.util.concurrent.ExecutorService;
2930 import java.util.concurrent.Executors;
2931 import java.util.concurrent.Semaphore;
2932 
2933 /** 
2934 * Java线程:新特征-信号量
2935 * 
2936 * @author leizhimin 2009-11-5 13:44:45 
2937 */ 
2938 publicclass Test {
2939         publicstaticvoid main(String[] args) {
2940                 MyPool myPool = new MyPool(20);
2941                 //创建线程池
2942                 ExecutorService threadPool = Executors.newFixedThreadPool(2); 
2943                 MyThread t1 = new MyThread("任务A", myPool, 3); 
2944                 MyThread t2 = new MyThread("任务B", myPool, 12); 
2945                 MyThread t3 = new MyThread("任务C", myPool, 7); 
2946                 //在线程池中执行任务
2947                 threadPool.execute(t1); 
2948                 threadPool.execute(t2); 
2949                 threadPool.execute(t3); 
2950                 //关闭池
2951                 threadPool.shutdown(); 
2952         } 
2953 } 
2954 
2955 /** 
2956 * 一个池 
2957 */ 
2958 class MyPool { 
2959         private Semaphore sp;    //池相关的信号量
2960 
2961         /** 
2962          * 池的大小,这个大小会传递给信号量
2963          * 
2964          * @param size 池的大小
2965          */ 
2966         MyPool(int size) {
2967                 this.sp =new Semaphore(size);
2968         } 
2969 
2970         public Semaphore getSp() {
2971                 return sp;
2972         } 
2973 
2974         publicvoid setSp(Semaphore sp) {
2975                 this.sp = sp;
2976         } 
2977 } 
2978 
2979 class MyThread extends Thread { 
2980         private String threadname;            //线程的名称
2981         private MyPool pool;                        //自定义池
2982         privateint x;                                    //申请信号量的大小
2983 
2984         MyThread(String threadname, MyPool pool, int x) {
2985                 this.threadname = threadname;
2986                 this.pool = pool;
2987                 this.x = x;
2988         } 
2989 
2990         publicvoid run() {
2991                 try {
2992                         //从此信号量获取给定数目的许可
2993                         pool.getSp().acquire(x); 
2994                         //todo:也许这里可以做更复杂的业务
2995                         System.out.println(threadname + "成功获取了" + x +"个许可!");
2996                 } catch (InterruptedException e) {
2997                         e.printStackTrace(); 
2998                 } finally {
2999                         //释放给定数目的许可,将其返回到信号量。
3000                         pool.getSp().release(x); 
3001                         System.out.println(threadname + "释放了" + x +"个许可!");
3002                 } 
3003         } 
3004 }
3005 
3006  
3007 
3008 任务B成功获取了12个许可!
3009 任务B释放了12个许可!
3010 任务A成功获取了3个许可!
3011 任务C成功获取了7个许可!
3012 任务C释放了7个许可!
3013 任务A释放了3个许可!
3014 
3015 Process finished with exit code 0
3016 
3017  
3018 
3019 从结果可以看出,信号量仅仅是对池资源进行监控,但不保证线程的安全,因此,在使用时候,应该自己控制线程的安全访问池资源。
3020 
3021  
3022 
3023  
3024 
3025 Java线程:新特征-阻塞队列
3026 阻塞队列是Java5线程新特征中的内容,Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素的操作会被阻塞等待,直到有空位为止。同样,当队列为空时候,请求队列元素的操作同样会阻塞等待,直到有可用元素为止。
3027 
3028  
3029 
3030 有了这样的功能,就为多线程的排队等候的模型实现开辟了便捷通道,非常有用。
3031 
3032  
3033 
3034 java.util.concurrent.BlockingQueue继承了java.util.Queue接口,可以参看API文档。
3035 
3036  
3037 
3038 下面给出一个简单应用的例子:
3039 
3040 import java.util.concurrent.BlockingQueue;
3041 import java.util.concurrent.ArrayBlockingQueue;
3042 
3043 /** 
3044 * Java线程:新特征-阻塞队列
3045 * 
3046 * @author leizhimin 2009-11-5 14:59:15 
3047 */ 
3048 publicclass Test {
3049         publicstaticvoid main(String[] args)throws InterruptedException {
3050                 BlockingQueue bqueue = new ArrayBlockingQueue(20);
3051                 for (int i = 0; i < 30; i++) {
3052                         //将指定元素添加到此队列中,如果没有可用空间,将一直等待(如果有必要)。
3053                         bqueue.put(i); 
3054                         System.out.println("向阻塞队列中添加了元素:" + i);
3055                 } 
3056                 System.out.println("程序到此运行结束,即将退出----");
3057         } 
3058 }
3059 
3060  
3061 
3062 输出结果:
3063 
3064 向阻塞队列中添加了元素:0
3065 向阻塞队列中添加了元素:1 
3066 向阻塞队列中添加了元素:2 
3067 向阻塞队列中添加了元素:3 
3068 向阻塞队列中添加了元素:4 
3069 向阻塞队列中添加了元素:5 
3070 向阻塞队列中添加了元素:6 
3071 向阻塞队列中添加了元素:7 
3072 向阻塞队列中添加了元素:8 
3073 向阻塞队列中添加了元素:9 
3074 向阻塞队列中添加了元素:10 
3075 向阻塞队列中添加了元素:11 
3076 向阻塞队列中添加了元素:12 
3077 向阻塞队列中添加了元素:13 
3078 向阻塞队列中添加了元素:14 
3079 向阻塞队列中添加了元素:15 
3080 向阻塞队列中添加了元素:16 
3081 向阻塞队列中添加了元素:17 
3082 向阻塞队列中添加了元素:18 
3083 向阻塞队列中添加了元素:19
3084 
3085  
3086 
3087 可以看出,输出到元素19时候,就一直处于等待状态,因为队列满了,程序阻塞了。
3088 
3089  
3090 
3091 这里没有用多线程来演示,没有这个必要。
3092 
3093  
3094 
3095 另外,阻塞队列还有更多实现类,用来满足各种复杂的需求:ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue,具体的API差别也很小。
3096 
3097 Java线程:新特征-阻塞栈
3098 对于阻塞栈,与阻塞队列相似。不同点在于栈是“后入先出”的结构,每次操作的是栈顶,而队列是“先进先出”的结构,每次操作的是队列头。
3099 
3100  
3101 
3102 这里要特别说明一点的是,阻塞栈是Java6的新特征。、
3103 
3104  
3105 
3106 Java为阻塞栈定义了接口:java.util.concurrent.BlockingDeque,其实现类也比较多,具体可以查看JavaAPI文档。
3107 
3108  
3109 
3110 下面看一个简单例子:
3111 
3112  
3113 
3114 import java.util.concurrent.BlockingDeque;
3115 import java.util.concurrent.LinkedBlockingDeque;
3116 
3117 /** 
3118 * Java线程:新特征-阻塞栈
3119 * 
3120 * @author leizhimin 2009-11-5 15:34:29 
3121 */ 
3122 publicclass Test {
3123         publicstaticvoid main(String[] args)throws InterruptedException {
3124                 BlockingDeque bDeque = new LinkedBlockingDeque(20);
3125                 for (int i = 0; i < 30; i++) {
3126                         //将指定元素添加到此阻塞栈中,如果没有可用空间,将一直等待(如果有必要)。
3127                         bDeque.putFirst(i); 
3128                         System.out.println("向阻塞栈中添加了元素:" + i);
3129                 } 
3130                 System.out.println("程序到此运行结束,即将退出----");
3131         } 
3132 }
3133 
3134  
3135 
3136 输出结果:
3137 
3138 向阻塞栈中添加了元素:0
3139 向阻塞栈中添加了元素:1 
3140 向阻塞栈中添加了元素:2 
3141 向阻塞栈中添加了元素:3 
3142 向阻塞栈中添加了元素:4 
3143 向阻塞栈中添加了元素:5 
3144 向阻塞栈中添加了元素:6 
3145 向阻塞栈中添加了元素:7 
3146 向阻塞栈中添加了元素:8 
3147 向阻塞栈中添加了元素:9 
3148 向阻塞栈中添加了元素:10 
3149 向阻塞栈中添加了元素:11 
3150 向阻塞栈中添加了元素:12 
3151 向阻塞栈中添加了元素:13 
3152 向阻塞栈中添加了元素:14 
3153 向阻塞栈中添加了元素:15 
3154 向阻塞栈中添加了元素:16 
3155 向阻塞栈中添加了元素:17 
3156 向阻塞栈中添加了元素:18 
3157 向阻塞栈中添加了元素:19
3158 
3159  
3160 
3161 从上面结果可以看到,程序并没结束,二是阻塞住了,原因是栈已经满了,后面追加元素的操作都被阻塞了。
3162 
3163 Java线程:新特征-条件变量
3164 条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量。但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往通过代码来赋予其含义。
3165 
3166  
3167 
3168 这里的条件和普通意义上的条件表达式有着天壤之别。
3169 
3170  
3171 
3172 条件变量都实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全。
3173 
3174  
3175 
3176 条件变量的出现是为了更精细控制线程等待与唤醒,在Java5之前,线程的等待与唤醒依靠的是Object对象的wait()和notify()/notifyAll()方法,这样的处理不够精细。
3177 
3178  
3179 
3180 而在Java5中,一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signalAll()方法,又可以唤醒该条件下的等待的线程。有关Condition接口的API可以具体参考JavaAPI文档。
3181 
3182  
3183 
3184 条件变量比较抽象,原因是他不是自然语言中的条件概念,而是程序控制的一种手段。
3185 
3186  
3187 
3188 下面以一个银行存取款的模拟程序为例来揭盖Java多线程条件变量的神秘面纱:
3189 
3190  
3191 
3192 有一个账户,多个用户(线程)在同时操作这个账户,有的存款有的取款,存款随便存,取款有限制,不能透支,任何试图透支的操作都将等待里面有足够存款才执行操作。
3193 
3194  
3195 
3196 import java.util.concurrent.ExecutorService;
3197 import java.util.concurrent.Executors;
3198 import java.util.concurrent.locks.Condition;
3199 import java.util.concurrent.locks.Lock;
3200 import java.util.concurrent.locks.ReentrantLock;
3201 
3202 /** 
3203 * Java线程:条件变量 
3204 * 
3205 * @author leizhimin 2009-11-5 10:57:29 
3206 */ 
3207 publicclass Test {
3208         publicstaticvoid main(String[] args) {
3209                 //创建并发访问的账户
3210                 MyCount myCount = new MyCount("95599200901215522", 10000);
3211                 //创建一个线程池
3212                 ExecutorService pool = Executors.newFixedThreadPool(2); 
3213                 Thread t1 = new SaveThread("张三", myCount, 2000); 
3214                 Thread t2 = new SaveThread("李四", myCount, 3600); 
3215                 Thread t3 = new DrawThread("王五", myCount, 2700); 
3216                 Thread t4 = new SaveThread("老张", myCount, 600); 
3217                 Thread t5 = new DrawThread("老牛", myCount, 1300); 
3218                 Thread t6 = new DrawThread("胖子", myCount, 800); 
3219                 //执行各个线程
3220                 pool.execute(t1); 
3221                 pool.execute(t2); 
3222                 pool.execute(t3); 
3223                 pool.execute(t4); 
3224                 pool.execute(t5); 
3225                 pool.execute(t6); 
3226                 //关闭线程池
3227                 pool.shutdown(); 
3228         } 
3229 } 
3230 
3231 /** 
3232 * 存款线程类 
3233 */ 
3234 class SaveThreadextends Thread {
3235         private String name;                //操作人
3236         private MyCount myCount;        //账户
3237         privateint x;                            //存款金额
3238 
3239         SaveThread(String name, MyCount myCount, int x) {
3240                 this.name = name;
3241                 this.myCount = myCount;
3242                 this.x = x;
3243         } 
3244 
3245         publicvoid run() {
3246                 myCount.saving(x, name); 
3247         } 
3248 } 
3249 
3250 /** 
3251 * 取款线程类 
3252 */ 
3253 class DrawThreadextends Thread {
3254         private String name;                //操作人
3255         private MyCount myCount;        //账户
3256         privateint x;                            //存款金额
3257 
3258         DrawThread(String name, MyCount myCount, int x) {
3259                 this.name = name;
3260                 this.myCount = myCount;
3261                 this.x = x;
3262         } 
3263 
3264         publicvoid run() {
3265                 myCount.drawing(x, name); 
3266         } 
3267 } 
3268 
3269 
3270 /** 
3271 * 普通银行账户,不可透支 
3272 */ 
3273 class MyCount { 
3274         private String oid;                        //账号
3275         privateint cash;                            //账户余额
3276         private Lock lock =new ReentrantLock();                //账户锁
3277         private Condition _save = lock.newCondition();    //存款条件
3278         private Condition _draw = lock.newCondition();    //取款条件
3279 
3280         MyCount(String oid, int cash) {
3281                 this.oid = oid;
3282                 this.cash = cash;
3283         } 
3284 
3285         /** 
3286          * 存款 
3287          * 
3288          * @param x        操作金额
3289          * @param name 操作人
3290          */ 
3291         publicvoid saving(int x, String name) {
3292                 lock.lock();                        //获取锁
3293                 if (x > 0) {
3294                         cash += x;                    //存款
3295                         System.out.println(name + "存款" + x +",当前余额为" + cash);
3296                 } 
3297                 _draw.signalAll();            //唤醒所有等待线程。
3298                 lock.unlock();                    //释放锁
3299         } 
3300 
3301         /** 
3302          * 取款 
3303          * 
3304          * @param x        操作金额
3305          * @param name 操作人
3306          */ 
3307         publicvoid drawing(int x, String name) {
3308                 lock.lock();                                 //获取锁
3309                 try {
3310                         if (cash - x < 0) {
3311                                 _draw.await();             //阻塞取款操作
3312                         } else {
3313                                 cash -= x;                     //取款
3314                                 System.out.println(name + "取款" + x +",当前余额为" + cash);
3315                         } 
3316                         _save.signalAll();             //唤醒所有存款操作
3317                 } catch (InterruptedException e) {
3318                         e.printStackTrace(); 
3319                 } finally {
3320                         lock.unlock();                     //释放锁
3321                 } 
3322         } 
3323 }
3324 
3325  
3326 
3327  
3328 
3329 李四存款3600,当前余额为13600
3330 张三存款2000,当前余额为15600
3331 老张存款600,当前余额为16200
3332 老牛取款1300,当前余额为14900
3333 胖子取款800,当前余额为14100
3334 王五取款2700,当前余额为11400
3335 
3336 Process finished with exit code 0
3337 
3338  
3339 
3340 假如我们不用锁和条件变量,如何实现此功能呢?下面是实现代码:
3341 
3342 import java.util.concurrent.ExecutorService;
3343 import java.util.concurrent.Executors;
3344 
3345 /** 
3346 * Java线程:不用条件变量
3347 * 
3348 * @author leizhimin 2009-11-5 10:57:29 
3349 */ 
3350 publicclass Test {
3351         publicstaticvoid main(String[] args) {
3352                 //创建并发访问的账户
3353                 MyCount myCount = new MyCount("95599200901215522", 10000);
3354                 //创建一个线程池
3355                 ExecutorService pool = Executors.newFixedThreadPool(2); 
3356                 Thread t1 = new SaveThread("张三", myCount, 2000); 
3357                 Thread t2 = new SaveThread("李四", myCount, 3600); 
3358                 Thread t3 = new DrawThread("王五", myCount, 2700); 
3359                 Thread t4 = new SaveThread("老张", myCount, 600); 
3360                 Thread t5 = new DrawThread("老牛", myCount, 1300); 
3361                 Thread t6 = new DrawThread("胖子", myCount, 800); 
3362                 //执行各个线程
3363                 pool.execute(t1); 
3364                 pool.execute(t2); 
3365                 pool.execute(t3); 
3366                 pool.execute(t4); 
3367                 pool.execute(t5); 
3368                 pool.execute(t6); 
3369                 //关闭线程池
3370                 pool.shutdown(); 
3371         } 
3372 } 
3373 
3374 /** 
3375 * 存款线程类 
3376 */ 
3377 class SaveThreadextends Thread {
3378         private String name;                //操作人
3379         private MyCount myCount;        //账户
3380         privateint x;                            //存款金额
3381 
3382         SaveThread(String name, MyCount myCount, int x) {
3383                 this.name = name;
3384                 this.myCount = myCount;
3385                 this.x = x;
3386         } 
3387 
3388         publicvoid run() {
3389                 myCount.saving(x, name); 
3390         } 
3391 } 
3392 
3393 /** 
3394 * 取款线程类 
3395 */ 
3396 class DrawThreadextends Thread {
3397         private String name;                //操作人
3398         private MyCount myCount;        //账户
3399         privateint x;                            //存款金额
3400 
3401         DrawThread(String name, MyCount myCount, int x) {
3402                 this.name = name;
3403                 this.myCount = myCount;
3404                 this.x = x;
3405         } 
3406 
3407         publicvoid run() {
3408                 myCount.drawing(x, name); 
3409         } 
3410 } 
3411 
3412 
3413 /** 
3414 * 普通银行账户,不可透支 
3415 */ 
3416 class MyCount { 
3417         private String oid;                        //账号
3418         privateint cash;                            //账户余额
3419 
3420         MyCount(String oid, int cash) {
3421                 this.oid = oid;
3422                 this.cash = cash;
3423         } 
3424 
3425         /** 
3426          * 存款 
3427          * 
3428          * @param x        操作金额
3429          * @param name 操作人
3430          */ 
3431         publicsynchronizedvoid saving(int x, String name) {
3432                 if (x > 0) {
3433                         cash += x;                    //存款
3434                         System.out.println(name + "存款" + x +",当前余额为" + cash);
3435                 } 
3436                 notifyAll();            //唤醒所有等待线程。
3437         } 
3438 
3439         /** 
3440          * 取款 
3441          * 
3442          * @param x        操作金额
3443          * @param name 操作人
3444          */ 
3445         publicsynchronizedvoid drawing(int x, String name) {
3446                 if (cash - x < 0) {
3447                         try {
3448                                 wait(); 
3449                         } catch (InterruptedException e1) {
3450                                 e1.printStackTrace(); 
3451                         } 
3452                 } else {
3453                         cash -= x;                     //取款
3454                         System.out.println(name + "取款" + x +",当前余额为" + cash);
3455                 } 
3456                 notifyAll();             //唤醒所有存款操作
3457         } 
3458 }
3459 
3460  
3461 
3462 输出结果为:
3463 
3464 李四存款3600,当前余额为13600
3465 王五取款2700,当前余额为10900
3466 老张存款600,当前余额为11500
3467 老牛取款1300,当前余额为10200
3468 胖子取款800,当前余额为9400
3469 张三存款2000,当前余额为11400
3470 
3471 Process finished with exit code 0
3472 
3473  
3474 
3475 结合先前同步代码知识,举一反三,将此例改为同步代码块来实现,代码如下:
3476 
3477 import java.util.concurrent.ExecutorService;
3478 import java.util.concurrent.Executors;
3479 
3480 /** 
3481 * Java线程:改为同步代码块
3482 * 
3483 * @author leizhimin 2009-11-5 10:57:29 
3484 */ 
3485 publicclass Test {
3486         publicstaticvoid main(String[] args) {
3487                 //创建并发访问的账户
3488                 MyCount myCount = new MyCount("95599200901215522", 10000);
3489                 //创建一个线程池
3490                 ExecutorService pool = Executors.newFixedThreadPool(2); 
3491                 Thread t1 = new SaveThread("张三", myCount, 2000); 
3492                 Thread t2 = new SaveThread("李四", myCount, 3600); 
3493                 Thread t3 = new DrawThread("王五", myCount, 2700); 
3494                 Thread t4 = new SaveThread("老张", myCount, 600); 
3495                 Thread t5 = new DrawThread("老牛", myCount, 1300); 
3496                 Thread t6 = new DrawThread("胖子", myCount, 800); 
3497                 //执行各个线程
3498                 pool.execute(t1); 
3499                 pool.execute(t2); 
3500                 pool.execute(t3); 
3501                 pool.execute(t4); 
3502                 pool.execute(t5); 
3503                 pool.execute(t6); 
3504                 //关闭线程池
3505                 pool.shutdown(); 
3506         } 
3507 } 
3508 
3509 /** 
3510 * 存款线程类 
3511 */ 
3512 class SaveThreadextends Thread {
3513         private String name;                //操作人
3514         private MyCount myCount;        //账户
3515         privateint x;                            //存款金额
3516 
3517         SaveThread(String name, MyCount myCount, int x) {
3518                 this.name = name;
3519                 this.myCount = myCount;
3520                 this.x = x;
3521         } 
3522 
3523         publicvoid run() {
3524                 myCount.saving(x, name); 
3525         } 
3526 } 
3527 
3528 /** 
3529 * 取款线程类 
3530 */ 
3531 class DrawThreadextends Thread {
3532         private String name;                //操作人
3533         private MyCount myCount;        //账户
3534         privateint x;                            //存款金额
3535 
3536         DrawThread(String name, MyCount myCount, int x) {
3537                 this.name = name;
3538                 this.myCount = myCount;
3539                 this.x = x;
3540         } 
3541 
3542         publicvoid run() {
3543                 myCount.drawing(x, name); 
3544         } 
3545 } 
3546 
3547 
3548 /** 
3549 * 普通银行账户,不可透支 
3550 */ 
3551 class MyCount { 
3552         private String oid;                        //账号
3553         privateint cash;                            //账户余额
3554 
3555         MyCount(String oid, int cash) {
3556                 this.oid = oid;
3557                 this.cash = cash;
3558         } 
3559 
3560         /** 
3561          * 存款 
3562          * 
3563          * @param x        操作金额
3564          * @param name 操作人
3565          */ 
3566         publicvoid saving(int x, String name) {
3567                 if (x > 0) {
3568                         synchronized (this) {
3569                                 cash += x;                    //存款
3570                                 System.out.println(name + "存款" + x +",当前余额为" + cash);
3571                                 notifyAll();            //唤醒所有等待线程。
3572                         } 
3573                 } 
3574         } 
3575 
3576         /** 
3577          * 取款 
3578          * 
3579          * @param x        操作金额
3580          * @param name 操作人
3581          */ 
3582         publicsynchronizedvoid drawing(int x, String name) {
3583                 synchronized (this) {
3584                         if (cash - x < 0) {
3585                                 try {
3586                                         wait(); 
3587                                 } catch (InterruptedException e1) {
3588                                         e1.printStackTrace(); 
3589                                 } 
3590                         } else {
3591                                 cash -= x;                     //取款
3592                                 System.out.println(name + "取款" + x +",当前余额为" + cash);
3593                         } 
3594                 } 
3595                 notifyAll();             //唤醒所有存款操作
3596         } 
3597 }
3598 
3599  
3600 
3601 李四存款3600,当前余额为13600
3602 王五取款2700,当前余额为10900
3603 老张存款600,当前余额为11500
3604 老牛取款1300,当前余额为10200
3605 胖子取款800,当前余额为9400
3606 张三存款2000,当前余额为11400
3607 
3608 Process finished with exit code 0
3609 
3610  
3611 
3612 对比以上三种方式,从控制角度上讲,第一种最灵活,第二种代码最简单,第三种容易犯错。
3613 
3614 Java线程:新特征-原子量
3615 所谓的原子量即操作变量的操作是“原子的”,该操作不可再分,因此是线程安全的。
3616 
3617  
3618 
3619 为何要使用原子变量呢,原因是多个线程对单个变量操作也会引起一些问题。在Java5之前,可以通过volatile、synchronized关键字来解决并发访问的安全问题,但这样太麻烦。
3620 
3621 Java5之后,专门提供了用来进行单变量多线程并发安全访问的工具包java.util.concurrent.atomic,其中的类也很简单。
3622 
3623  
3624 
3625 下面给出一个反面例子(切勿模仿):
3626 
3627 import java.util.concurrent.ExecutorService;
3628 import java.util.concurrent.Executors;
3629 import java.util.concurrent.atomic.AtomicLong;
3630 
3631 /** 
3632 * Java线程:新特征-原子量
3633 * 
3634 * @author leizhimin 2009-11-6 9:53:11 
3635 */ 
3636 publicclass Test {
3637         publicstaticvoid main(String[] args) {
3638                 ExecutorService pool = Executors.newFixedThreadPool(2); 
3639                 Runnable t1 = new MyRunnable("张三", 2000);
3640                 Runnable t2 = new MyRunnable("李四", 3600);
3641                 Runnable t3 = new MyRunnable("王五", 2700);
3642                 Runnable t4 = new MyRunnable("老张", 600);
3643                 Runnable t5 = new MyRunnable("老牛", 1300);
3644                 Runnable t6 = new MyRunnable("胖子", 800);
3645                 //执行各个线程
3646                 pool.execute(t1); 
3647                 pool.execute(t2); 
3648                 pool.execute(t3); 
3649                 pool.execute(t4); 
3650                 pool.execute(t5); 
3651                 pool.execute(t6); 
3652                 //关闭线程池
3653                 pool.shutdown(); 
3654         } 
3655 } 
3656 
3657 class MyRunnableimplements Runnable {
3658         privatestatic AtomicLong aLong =new AtomicLong(10000);        //原子量,每个线程都可以自由操作
3659         private String name;                //操作人
3660         privateint x;                            //操作数额
3661 
3662         MyRunnable(String name, int x) {
3663                 this.name = name;
3664                 this.x = x;
3665         } 
3666 
3667         publicvoid run() {
3668                 System.out.println(name + "执行了" + x +",当前余额:" + aLong.addAndGet(x));
3669         } 
3670 }
3671 
3672  
3673 
3674 运行结果:
3675 
3676 李四执行了3600,当前余额:13600
3677 王五执行了2700,当前余额:16300
3678 老张执行了600,当前余额:16900
3679 老牛执行了1300,当前余额:18200
3680 胖子执行了800,当前余额:19000
3681 张三执行了2000,当前余额:21000
3682 
3683 Process finished with exit code 0
3684 
3685  
3686 
3687 张三执行了2000,当前余额:12000
3688 王五执行了2700,当前余额:18300
3689 老张执行了600,当前余额:18900
3690 老牛执行了1300,当前余额:20200
3691 胖子执行了800,当前余额:21000
3692 李四执行了3600,当前余额:15600
3693 
3694 Process finished with exit code 0
3695 
3696  
3697 
3698 张三执行了2000,当前余额:12000
3699 李四执行了3600,当前余额:15600
3700 老张执行了600,当前余额:18900
3701 老牛执行了1300,当前余额:20200
3702 胖子执行了800,当前余额:21000
3703 王五执行了2700,当前余额:18300
3704 
3705 Process finished with exit code 0
3706 
3707  
3708 
3709 从运行结果可以看出,虽然使用了原子量,但是程序并发访问还是有问题,那究竟问题出在哪里了?
3710 
3711  
3712 
3713 这里要注意的一点是,原子量虽然可以保证单个变量在某一个操作过程的安全,但无法保证你整个代码块,或者整个程序的安全性。因此,通常还应该使用锁等同步机制来控制整个程序的安全性。
3714 
3715  
3716 
3717 下面是对这个错误修正:
3718 
3719 import java.util.concurrent.ExecutorService;
3720 import java.util.concurrent.Executors;
3721 import java.util.concurrent.locks.Lock;
3722 import java.util.concurrent.locks.ReentrantLock;
3723 import java.util.concurrent.atomic.AtomicLong;
3724 
3725 /** 
3726 * Java线程:新特征-原子量
3727 * 
3728 * @author leizhimin 2009-11-6 9:53:11 
3729 */ 
3730 publicclass Test {
3731         publicstaticvoid main(String[] args) {
3732                 ExecutorService pool = Executors.newFixedThreadPool(2); 
3733                 Lock lock = new ReentrantLock(false);
3734                 Runnable t1 = new MyRunnable("张三", 2000,lock);
3735                 Runnable t2 = new MyRunnable("李四", 3600,lock);
3736                 Runnable t3 = new MyRunnable("王五", 2700,lock);
3737                 Runnable t4 = new MyRunnable("老张", 600,lock);
3738                 Runnable t5 = new MyRunnable("老牛", 1300,lock);
3739                 Runnable t6 = new MyRunnable("胖子", 800,lock);
3740                 //执行各个线程
3741                 pool.execute(t1); 
3742                 pool.execute(t2); 
3743                 pool.execute(t3); 
3744                 pool.execute(t4); 
3745                 pool.execute(t5); 
3746                 pool.execute(t6); 
3747                 //关闭线程池
3748                 pool.shutdown(); 
3749         } 
3750 } 
3751 
3752 class MyRunnableimplements Runnable {
3753         privatestatic AtomicLong aLong =new AtomicLong(10000);        //原子量,每个线程都可以自由操作
3754         private String name;                //操作人
3755         privateint x;                            //操作数额
3756         private Lock lock;
3757 
3758         MyRunnable(String name, int x,Lock lock) {
3759                 this.name = name;
3760                 this.x = x;
3761                 this.lock = lock;
3762         } 
3763 
3764         publicvoid run() {
3765                 lock.lock(); 
3766                 System.out.println(name + "执行了" + x +",当前余额:" + aLong.addAndGet(x));
3767                 lock.unlock(); 
3768         } 
3769 }
3770 
3771  
3772 
3773 执行结果:
3774 
3775 张三执行了2000,当前余额:12000
3776 王五执行了2700,当前余额:14700
3777 老张执行了600,当前余额:15300
3778 老牛执行了1300,当前余额:16600
3779 胖子执行了800,当前余额:17400
3780 李四执行了3600,当前余额:21000
3781 
3782 Process finished with exit code 0
3783 
3784  
3785 
3786 这里使用了一个对象锁,来控制对并发代码的访问。不管运行多少次,执行次序如何,最终余额均为21000,这个结果是正确的。
3787 
3788  
3789 
3790 有关原子量的用法很简单,关键是对原子量的认识,原子仅仅是保证变量操作的原子性,但整个程序还需要考虑线程安全的。
3791 
3792 Java线程:新特征-障碍器
3793 Java5中,添加了障碍器类,为了适应一种新的设计需求,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择障碍器了。
3794 
3795  
3796 
3797 障碍器是多线程并发控制的一种手段,用法很简单。下面给个例子:
3798 
3799  
3800 
3801 import java.util.concurrent.BrokenBarrierException;
3802 import java.util.concurrent.CyclicBarrier;
3803 
3804 /** 
3805 * Java线程:新特征-障碍器
3806 * 
3807 * @author leizhimin 2009-11-6 10:50:10 
3808 */ 
3809 publicclass Test {
3810         publicstaticvoid main(String[] args) {
3811                 //创建障碍器,并设置MainTask为所有定数量的线程都达到障碍点时候所要执行的任务(Runnable)
3812                 CyclicBarrier cb = new CyclicBarrier(7,new MainTask());
3813                 new SubTask("A", cb).start();
3814                 new SubTask("B", cb).start();
3815                 new SubTask("C", cb).start();
3816                 new SubTask("D", cb).start();
3817                 new SubTask("E", cb).start();
3818                 new SubTask("F", cb).start();
3819                 new SubTask("G", cb).start();
3820         } 
3821 } 
3822 
3823 /** 
3824 * 主任务 
3825 */ 
3826 class MainTask implements Runnable { 
3827         publicvoid run() {
3828                 System.out.println(">>>>主任务执行了!<<<<");
3829         } 
3830 } 
3831 
3832 /** 
3833 * 子任务 
3834 */ 
3835 class SubTask extends Thread { 
3836         private String name;
3837         private CyclicBarrier cb;
3838 
3839         SubTask(String name, CyclicBarrier cb) { 
3840                 this.name = name;
3841                 this.cb = cb;
3842         } 
3843 
3844         publicvoid run() {
3845                 System.out.println("[子任务" + name +"]开始执行了!");
3846                 for (int i = 0; i < 999999; i++) ;    //模拟耗时的任务
3847                 System.out.println("[子任务" + name +"]开始执行完成了,并通知障碍器已经完成!");
3848                 try {
3849                         //通知障碍器已经完成
3850                         cb.await(); 
3851                 } catch (InterruptedException e) {
3852                         e.printStackTrace(); 
3853                 } catch (BrokenBarrierException e) {
3854                         e.printStackTrace(); 
3855                 } 
3856         } 
3857 }
3858 
3859  
3860 
3861 运行结果:
3862 
3863 [子任务E]开始执行了!
3864 [子任务E]开始执行完成了,并通知障碍器已经完成!
3865 [子任务F]开始执行了!
3866 [子任务G]开始执行了!
3867 [子任务F]开始执行完成了,并通知障碍器已经完成!
3868 [子任务G]开始执行完成了,并通知障碍器已经完成!
3869 [子任务C]开始执行了!
3870 [子任务B]开始执行了!
3871 [子任务C]开始执行完成了,并通知障碍器已经完成!
3872 [子任务D]开始执行了!
3873 [子任务A]开始执行了!
3874 [子任务D]开始执行完成了,并通知障碍器已经完成!
3875 [子任务B]开始执行完成了,并通知障碍器已经完成!
3876 [子任务A]开始执行完成了,并通知障碍器已经完成!
3877 >>>>主任务执行了!<<<<
3878 
3879 Process finished with exit code 0
3880 
3881  
3882 
3883 从执行结果可以看出,所有子任务完成的时候,主任务执行了,达到了控制的目标。

 

以上是关于Java线程详解的主要内容,如果未能解决你的问题,请参考以下文章

Java 线程池详解

Java线程池详解

(转) Java中的负数及基本类型的转型详解

20160226.CCPP体系详解(0036天)

JAVA多线程synchronized详解

Java多线程:线程同步详解