线程同步、死锁和通信——Java多线程(二)
一、多线程同步
上一篇随笔中,我曾遇到对多线程程序的多次运行结果不一致的情况,这主要是因为没有对这些线程在访问临界资源做必要的控制,而接下来就用线程的同步来解决这个问题。
1.同步代码块
1 class RunnableDemo implements Runnable 2 { 3 private int tickets=5; 4 public void run() 5 { 6 while(true) 7 { 8 synchronized(this)//同步代码块语法定义如下 9 { 10 if(tickets<=0) break; 11 try{ 12 Thread.sleep(100); 13 } 14 catch(Exception e){ 15 e.printStackTrace(); 16 } 17 System.out.println(Thread.currentThread().getName()+"出售票:"+tickets); 18 tickets -= 1; 19 } 20 } 21 } 22 } 23 24 public class ThreadTest 25 { 26 public static void main(String[] args) 27 { 28 RunnableDemo r = new RunnableDemo(); 29 new Thread(r).start(); 30 new Thread(r).start(); 31 new Thread(r).start(); 32 new Thread(r).start(); 33 } 34 }
在同一时刻只能由一个线程进入同步代码块内运行,只有当该线程离开同步代码块后,其他线程才能进入同步代码块内运行。
2.同步方法
即:把上述例子中同步代码块的内容,专门封装在一个方法里,通过在run()方法中调用创建的方法实现相应的功能。
1 class RunnableDemo implements Runnable 2 { 3 private int tickets=5; 4 public void run() 5 { 6 while(tickets>0) 7 { 8 sale(); 9 } 10 } 11 public synchronized void sale()//同步方法 12 { 13 if(tickets>0){ 14 try{ 15 Thread.sleep(100); 16 } 17 catch(Exception e){ 18 e.printStackTrace(); 19 } 20 System.out.println(Thread.currentThread().getName()+"出售票:"+tickets); 21 tickets -= 1; 22 } 23 } 24 } 25 26 public class ThreadTest 27 { 28 public static void main(String[] args) 29 { 30 RunnableDemo r = new RunnableDemo(); 31 new Thread(r).start(); 32 new Thread(r).start(); 33 new Thread(r).start(); 34 new Thread(r).start(); 35 } 36 }
二、死锁
如果有一组进程(或线程),线程1 已经占据资源R1,并持有资源R1上的锁,而且正在等待资源R2开锁;线程2已经占据资源R2,并拥有资源R2上的锁,却正在等待R1开锁。那么这两个线程都不释放自己占据的资源,同时申请不到对方资源上的锁,它们只能永远等待下去。这种现象就叫做死锁。
预防死锁的一种方法:利用有序资源分配策略——要求线程申请资源必须按照以编号上升的次序依次申请。
三、线程间通信
同属于一个进程的多个线程,是共享地址空间的,它们可以相互通信,共同协作来完成指定任务。
Java是通过Object类的wait()、notify()、notifyAll()这几个方法来实现线程间的通信。
wait():线程进入睡眠状态,直到其他线程进入并调用notify()或notifyAll()为止。
notify():唤醒在该同步代码块中第1个调用wait()的线程。
notifyAll():唤醒在该同步代码块中所有调用wait()的线程,高优先级最先被唤醒。
1 class Producer implements Runnable 2 { 3 Person q = null; 4 public Producer(Person q) 5 { 6 this.q=q; 7 } 8 @Override 9 public void run() 10 { 11 for(int i=0;i<10;i++) 12 { 13 if(i%2==0) 14 { 15 q.set("张三","男"); 16 } 17 else{ 18 q.set("李四","女"); 19 } 20 } 21 } 22 } 23 class Consumer implements Runnable 24 { 25 Person q = null; 26 public Consumer(Person q) 27 { 28 this.q=q; 29 } 30 @Override 31 public void run() 32 { 33 for(int i=0;i<10;++i) 34 { 35 q.get(); 36 } 37 } 38 } 39 class Person 40 { 41 private String name = "李四"; 42 private String sex = "女"; 43 private boolean bFull = false;//当Consumer线程取走数据后,false 44 public synchronized void set(String name,String sex) 45 { 46 if(bFull) 47 { 48 try 49 { 50 wait();//后来的线程要等待 51 }catch(InterruptedException e){ 52 e.printStackTrace(); 53 } 54 } 55 this.name = name; 56 this.sex = sex; 57 bFull = true;//当Producer线程放入数据后,true 58 notify();//唤醒最先到达的线程 59 } 60 public synchronized void get() 61 { 62 if(!bFull) 63 { 64 try{ 65 wait(); 66 }catch(InterruptedException e){ 67 e.printStackTrace(); 68 } 69 } 70 System.out.println(name+"——>"+sex); 71 bFull = false; 72 notify(); 73 } 74 } 75 public class ThreadCommunation 76 { 77 public static void main(String[] args) 78 { 79 Person q = new Person(); 80 new Thread(new Producer(q)).start(); 81 new Thread(new Consumer(q)).start(); 82 } 83 }
***注意:wait()、notify()、notifyAll()这三个方法必须在synchronized方法中调用,该线程必须得到该对象的所有权。
四、线程的生命周期
控制线程生命周期的方法:suspend()、resume()、stop()方法,但是这三个方法都不推荐使用。
若想控制线程的生命周期,推荐使用在run()方法中添加循环条件的方法来实现对线程生命周期的控制。