线程的状态与线程安全

  线程也有生命周期,线程从创建到死亡会经历如下的过程:

    创建(new)-->待运行(runnable)-->运行(running)-->死亡(dead)

              |_____阻塞______|(阻塞过程包含waiting、sleeping、yeild、join、blocked)

  下面将一一讲诉具体的每个过程。前面讲到怎样创建一个线程,但创建完一个线程后此线程并不会马上进入到运行状态,而是要等分配到一定的资源(比如内存空

间)后变为runnable,当调用start()方法后,线程便开始运行。如果此时cpu出于空闲,则会去执行此线程,否则就会出于blocked状态,等待cpu的执行权。这里的运行线程是调用start(),而不是run(),调用run()只是当前的线程去执行此方法,而不是开启新的线程。

  waiting、sleeping、yeild、blocked都是让线程出于阻塞状态,那么彼此之间又有什么区别。首先wait()方法是让线程出于等待状态,此时线程会交出cpu的执

行权,如果线程此时持有锁还会释放出锁;sleep()同样是让线程出于睡眠状态,但是线程只会交出cpu的执行权,并不会释放手中持有的锁;yeild()是让当前运行

的线程变成待运行的状态,cpu再从同等优先级的线程中选择某个线程去执行(还是有可能会选择到此线程);join()会暂停当前线程的运行去执行调用此方法的

线程,等到调用的线程执行完毕后再回过来执行当前线程;当cpu执行其他线程时,那些没有得到执行权的线程就处于blocked状态,还有就是线程处于等待锁的时候

也处于blocked状态。

  我觉得这里重点需要注意的就是wait()与notify()。用一道面试题说明这个问题。题目是:顺序输出A、B、C十次。

 

  1 package OutputABC;
  2 
  3 public class ABCTest1 {    //三个标志位,作用是控制A、B、C输出的顺序
  4     Boolean flagA = true;
  5     Boolean flagB = false;
  6     Boolean flagC = false;
  7     Object lock = new Object();
  8     public static void main(String[] args) {
  9         ABCTest1 test = new ABCTest1();
 10         test.new ThreadA().start();
 11         try {
 12             Thread.sleep(1000);
 13         } catch (InterruptedException e) {
 14             // TODO Auto-generated catch block
 15             e.printStackTrace();
 16         }
 17         test.new ThreadB().start();
 18         try {
 19             Thread.sleep(1000);
 20         } catch (InterruptedException e) {
 21             // TODO Auto-generated catch block
 22             e.printStackTrace();
 23         }
 24         test.new ThreadC().start();
 25         
 26     }
 27     //线程1,输出A
 28     class ThreadA extends Thread{
 29         @Override
 30         public void run() {
 31             // TODO Auto-generated method stub             //加锁
 32             synchronized(lock){          //循环十次
 33                 for(int i = 0;i<10;i++){              //进行判断是否该输出A了
 34                     if(flagA){                //输出A,同时置标志位B为true
 35                         System.out.println("A");
 36                         flagA = false;
 37                         flagB = true;                //通知其他线程醒来
 38                         lock.notifyAll();
 39                         try {                  //自己等待
 40                             lock.wait();
 41                         } catch (InterruptedException e) {
 42                             // TODO Auto-generated catch block
 43                             e.printStackTrace();
 44                         }
 45                     }              //如果不是就继续等待
 46                     else{
 47                     try {
 48                         lock.wait();                //这个必须要,假设当输出B后,此时A被唤醒,而B会交出锁,A如果这个时候抢占到了执行权就会去执行for循环i++,很明显会使A的输出减少1次                //大家可以试一试,如果去掉了i--,此时只会输出5次
 49                         i--;
 50                     } catch (InterruptedException e) {
 51                         // TODO Auto-generated catch block
 52                         e.printStackTrace();
 53                     }
 54                     }
 55                 }
 56             }
 57         }
 58     }
 59     
 60     class ThreadB extends Thread{
 61         @Override
 62         public void run() {
 63             // TODO Auto-generated method stub
 64             synchronized(lock){
 65                 for(int i = 0;i<10;i++){
 66                     if(flagB){
 67                         System.out.println("B");
 68                         flagB = false;
 69                         flagC = true;
 70                         lock.notifyAll();
 71                         try {
 72                             lock.wait();
 73                         } catch (InterruptedException e) {
 74                             // TODO Auto-generated catch block
 75                             e.printStackTrace();
 76                         }
 77                     }
 78                     else{
 79                         try {
 80                             lock.wait();
 81                             i--;
 82                         } catch (InterruptedException e) {
 83                             // TODO Auto-generated catch block
 84                             e.printStackTrace();
 85                         }
 86                         }
 87                 }
 88             }
 89         }
 90     }
 91     
 92     class ThreadC extends Thread{
 93             // TODO Auto-generated method stub
 94         @Override
 95         public void run() {
 96             // TODO Auto-generated method stub
 97             synchronized(lock){
 98                 for(int i = 0;i<10;i++){
 99                     if(flagC){
100                         System.out.println("C");
101                         flagC = false;
102                         flagA = true;
103                         lock.notifyAll();
104                         try {
105                             lock.wait();
106                         } catch (InterruptedException e) {
107                             // TODO Auto-generated catch block
108                             e.printStackTrace();
109                         }
110                     }
111                     else{
112                         try {
113                             lock.wait();
114                             i--;
115                         } catch (InterruptedException e) {
116                             // TODO Auto-generated catch block
117                             e.printStackTrace();
118                         }
119                         }
120                 }
121             }
122         }
123         }
124 }
View Code

 

 

  这道题用wait()与notify()做我只能想到用这个标志位的比较简单,想过用三个锁分别是ABC的,但在释放锁的时候感觉好麻烦。上面的注释已经很清楚了,这里就不再赘述。

至于用notify时报错IllegalMonitorStateException这里就不再说了,在java常见错误那篇文章中有详细的说明。

------------------------------------------------------------------------------------------------------------------------------------

  多线程容易产生安全问题,产生安全问题的原因就是当多个线程同时访问一个数据,并对它进行修改,此时就容易造成多次修改的危险。避免此问题的方法就是采用

同步。具体的实现有两种Lock和Synchronized,关键是需要找出需要同步的点。

 1 public class SynchroTest {
 2     //全局变量i,代表临街资源
 3     int i = 0;
 4     public static void main(String[] args) {
 5         SynchroTest test  = new SynchroTest();
 6         MyThraad1 my1 = test.new MyThraad1();
 7         //启动线程1,并人为让线程1进行判断后在休眠,给线程2的执行创造时间
 8         my1.start();
 9         MyThraad2 my2 = test.new MyThraad2();
10         my2.start();
11     }
12     
13     //线程1
14     class MyThraad1 extends Thread{
15         @Override
16         public void run() {
17             if(i==0){
18             try {
19                 Thread.currentThread().sleep(5000);
20             } catch (InterruptedException e) {
21                 e.printStackTrace();
22             }
23                 i++;
24             }
25             System.out.println(i);
26         }
27     }
28     //线程2
29     class MyThraad2 extends Thread{
30         @Override
31         public void run() {
32             if(i==0){
33                 i++;
34             }
35             System.out.println(i);
36         }
37     }
38 }
产生问题

  采用同步关键字Synchronized,可以是同步方法,也可以是同步代码块。同步方法比较简单,只需要在需要同步的方法前面加上此关键字。但是需要注意加的必须是同一把锁

看下面这段代码,很容易出现的错误。

 1 public class SynchroTest {
 2     //全局变量i,代表临街资源
 3     int i = 0;
 4     public static void main(String[] args) {
 5         SynchroTest test  = new SynchroTest();
 6         MyThraad1 my1 = test.new MyThraad1();
 7         //启动线程1,并人为让线程1进行判断后在休眠,给线程2的执行创造时间
 8         my1.start();
 9         MyThraad2 my2 = test.new MyThraad2();
10         my2.start();
11     }
12     
13     //线程1
14     class MyThraad1 extends Thread{
15         @Override
16         public synchronized void run() {
17             System.out.println(this);
18             if(i==0){
19             try {
20                 Thread.currentThread().sleep(1000);
21             } catch (InterruptedException e) {
22                 e.printStackTrace();
23             }
24                 i++;
25             }
26             System.out.println(i);
27         }
28     }
29     //线程2
30     class MyThraad2 extends Thread{
31         @Override
32         public synchronized void run() {
33             System.out.println(this);
34             if(i==0){
35                 i++;
36             }
37             System.out.println(i);
38         }
39     }
40 }
View Code

  如果你没有发现错误在哪里,我们看一下打印的结果就知道了。

        Thread[Thread-0,5,main]
        Thread[Thread-1,5,main]
        1
        2

  出现这个问题的原因就是不是同一把锁。最安全的方式就是自己创建锁这样就不用去判断this是谁了。

 1 public class SynchroTest1 {
 2     //全局变量i,代表数据库中的一个数据
 3     int i = 0;
 4     static SynchroTest1 test  = new SynchroTest1();
 5     public static void main(String[] args) {
 6         MyThraad1 my1 = test.new MyThraad1();
 7         //启动线程1,目的是修改数据库中的i
 8         my1.start();
 9         //代表执行过程中的异常
10         my1.interrupt();
11         //线程1休眠,代表之前执行过程比较慢,用户又重复执行一次
12         MyThraad2 my2 = test.new MyThraad2();
13         my2.start();
14     }
15     
16     //线程1
17     class MyThraad1 extends Thread{
18         @Override
19         public void run() {
20             synchronized (test) {
21                 System.out.println(test);
22                 if(i==0){
23                     i++;
24                 }
25                 System.out.println(i);
26             }
27         }
28     }
29     //线程2
30     class MyThraad2 extends Thread{
31         @Override
32         public void run() {
33             synchronized (test) {
34                 System.out.println(test);
35                 if(i==0){
36                     i++;
37                 }
38                 System.out.println(i);
39             }
40         }
41     }
42 }
View Code

  对于Lock锁还有它自己的一些特性,如它的子类ReenTrantLock,他是可以中断的,而在Synchronized中是只能等待线程自己释放锁;ReenTrantLock也有类似wait与

notify的方法,不过他是用Condition来完成的,个人觉得这个比Synchronized中的wait用起来方便一点,我们可以将锁的操作和等待操作分开。在java常见异常那篇文章中有

终端的解释,这里就来看看Condition是怎样用的。还是用上面的那个面试题,上面不是说用三个锁分别控制麻烦吗,但是在这里用Condition就可以很清晰的解决这个问题。

 1 import java.util.concurrent.locks.Condition;
 2 import java.util.concurrent.locks.ReentrantLock;
 3 
 4 //试想三个线程轮流输出123共十次
 5 public class ABCTest {
 6     ReentrantLock lock1 = new ReentrantLock();
    //A、B、C分别对应一个锁
7 Condition conA = lock1.newCondition(); 8 Condition conB = lock1.newCondition(); 9 Condition conC = lock1.newCondition(); 10 public static void main(String[] args) throws Exception { 11 ABCTest test = new ABCTest(); 12 test.new ThreadA().start(); 13 Thread.sleep(1000); 14 test.new ThreadB().start(); 15 Thread.sleep(1000); 16 test.new ThreadC().start(); 17 } 18 19 class ThreadA extends Thread{ 20 @Override 21 public void run() { 22 // TODO Auto-generated method stub 23 for(int i=0;i<10;i++){ 24 try{
              //上锁
25 lock1.lock(); 26 System.out.println("A"); 27 try {
                //这里是唤醒B,并让A等待,是不是好方便,压根就不用考虑锁的问题
28 conB.signal(); 29 conA.await(); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 34 }finally{ 35 lock1.unlock(); 36 } 37 } 38 } 39 } 40 41 class ThreadB extends Thread{ 42 @Override 43 public void run() { 44 // TODO Auto-generated method stub 45 for(int i=0;i<10;i++){ 46 try{ 47 lock1.lock(); 48 System.out.println("B"); 49 try { 50 conC.signal(); 51 conB.await(); 52 } catch (InterruptedException e) { 53 e.printStackTrace(); 54 } 55 56 }finally{ 57 lock1.unlock(); 58 } 59 } 60 } 61 } 62 63 class ThreadC extends Thread{ 64 @Override 65 public void run() { 66 // TODO Auto-generated method stub 67 for(int i = 0;i<10;i++){ 68 try{ 69 lock1.lock(); 70 System.out.println("C"); 71 try { 72 conA.signal(); 73 conC.await(); 74 } catch (InterruptedException e) { 75 e.printStackTrace(); 76 } 77 78 }finally{ 79 lock1.unlock(); 80 } 81 } 82 } 83 } 84 }

  这篇文章主要讲了线程的生命周期中出现的几种状态,以及wait常见的问题;还讲了多线程出现的安全问题,以及解决办法;在最后又以一个例子讲了ReentrantLock中的Condition。

 

posted @ 2015-04-18 22:45  codeZhu  阅读(258)  评论(0编辑  收藏  举报