zoukankan      html  css  js  c++  java
  • 线程的状态与线程安全

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

        创建(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。

  • 相关阅读:
    SQL分类
    广度/深度优先生成树
    图的基本概念
    哈夫曼树构造/哈夫曼编码
    二叉排序树/平衡二叉树
    树、森林与二叉树的转换
    树/二叉树的基本性质
    /*传说中的土办法找中序前驱*/
    KiCAD原理图更换库
    博客园添加版权信息
  • 原文地址:https://www.cnblogs.com/Jc-zhu/p/4433404.html
Copyright © 2011-2022 走看看