黑马程序员 学习总结之多线程

来源:互联网 发布:基于单片机音乐播放器 编辑:程序博客网 时间:2024/05/16 12:37

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------

一、进程与线程

      1,进程:就是正在执行的程序,所占有的内存的空间

      2,线程:其实就是进程中的一个子程序,就是一个独立的执行路径,独立的执行控制单元

二、创建线程的两种方式

        java中可以进行多线程的程序编程,其实线程是由操作系统开启,依靠java虚拟机来实现,线程也是对象,有了线程对象的描述类 java.lang.Thread,就可以进行多线程的编程。

        1,创建线程的第一种方式——继承Thread类

              继承Thread类的方式,数据是线程独享的,通过以下三步进行线程的创建

              1.1,定义类继承Thread

              1.2,复写Thread类的run方法

              1.3,创建线程子类对象,调用线程的start方法开启线程

[java] view plaincopy
  1. class Demo extends Thread  
  2. {  
  3.     public void run(){  
  4.        for(int x = 0 ; x < 20 ; x++){  
  5.           System.out.println("run..."+x);  
  6.        }  
  7.     }  
  8. }  
  9.   
  10. class ThreadDemo   
  11. {  
  12.     public static void main(String[] args)   
  13.     {  
  14.         Demo d = new Demo();  
  15.         d.start();  
  16.         for(int x = 0 ; x < 20 ; x++){  
  17.         System.out.println("main..."+x);  
  18.         }  
  19.     }  
  20. }  

       程序在执行到Demo d = new Demo()时创建线程d,执行d.start()时开启这个线程,此时存在两个线程主线程与线程d,run方法与main方法中的for语句交替执行。

       2,创建线程的第二种方式——实现Runnable接口

             继承有局限性,java中只能单继承,接口可以多实现,Runnable接口方式,数据是程共享的,通过实现Runnable接口创建线程通过以下步骤完成

             2.1,定义类实现Runnable接口

             2.2,复写Runnable接口的run方法

             2.3,通过Thread类建立线程对象

             2.4,将Runnable接口子类对象作为实际参数传递给Thread类的构造函数

                 2.5,调用Thread类的start方法开启线程并调用Runnable接口子类对象的run方法

[java] view plaincopy
  1. class Ticket implements Runnable  
  2. {  
  3.     private int tickets = 100;  
  4.     public void run(){  
  5.         while(true){  
  6.             if(tickets > 0){  
  7.                 System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);  
  8.             }  
  9.         }  
  10.     }  
  11. }  
  12. class TicketDemo   
  13. {  
  14.     public static void main(String[] args)   
  15.     {  
  16.         Ticket t = new Ticket();  
  17.         Thread t1 = new Thread(t);  
  18.         Thread t2 = new Thread(t);  
  19.         Thread t3 = new Thread(t);  
  20.         Thread t4 = new Thread(t);  
  21.         t1.setName("一号窗口");  
  22.         t2.setName("二号窗口");  
  23.         t3.setName("三号窗口");  
  24.         t4.setName("四号窗口");  
  25.         t1.start();  
  26.                 t2.start();  
  27.                 t3.start();  
  28.                 t4.start();  
  29.     }  
  30. }  

三、多线程的安全问题

       1,原因

             多线程操作同一个共享数据的时候,会发生数据安全问题,以售票系统为例,if(tickets > 0)与System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--)这两句话操作共享数据tickets,这样就会出现当一个线程池判断tickets=1时,进入if语句,还未执行到tickets--),时第二个线程进入判断tickets > 0也进入if语句,这样就会导致出现负票的现象。下面程序中使用wait方法让线程还没有执行完所有操作共享数据的语句就停下来,然后让其它线程进来执行来造成打印负票的现象

[java] view plaincopy
  1. class Ticket implements Runnable  
  2. {  
  3.     private int tickets = 100;  
  4.     public void run(){  
  5.         while(true){  
  6.             if(tickets > 0){  
  7.                try{  
  8.                Thread.sleep(10);  
  9.                }catch(Exception e){}  
  10.               System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);  
  11.             }  
  12.         }  
  13.     }  
  14. }  

        2,解决办法

              2.1,思路:让一个线程执行操作共享数据的多条语句时,其他线程执行不了,当这个线程执行完后其它线程才能执行

              2.2,办法:同步机制synchronized(对象){线程操作的共享数据 },同步中的对象,看成一把锁,线程进到同步代码块后,拥有了锁,没有锁的线程,就不会执行,当有锁的线程执行完毕后,会释放锁其他线程就开始抢锁。

[java] view plaincopy
  1. class Ticket implements Runnable  
  2. {  
  3.     private int tickets = 100;  
  4.     public void run(){  
  5.         while(true){  
  6.            synchronized(this){//同步机制  
  7.              if(tickets > 0){  
  8.                try{  
  9.                Thread.sleep(10);  
  10.                }catch(Exception e){}  
  11.               System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);  
  12.             }  
  13.            }  
  14.         }  
  15.     }  
  16. }  
  17. class TicketDemo   
  18. {  
  19.     public static void main(String[] args)   
  20.     {  
  21.         Ticket t = new Ticket();  
  22.         Thread t1 = new Thread(t);  
  23.         Thread t2 = new Thread(t);  
  24.         Thread t3 = new Thread(t);  
  25.         Thread t4 = new Thread(t);  
  26.         t1.setName("一号窗口");  
  27.         t2.setName("二号窗口");  
  28.         t3.setName("三号窗口");  
  29.         t4.setName("四号窗口");  
  30.         t1.start();  
  31.         t2.start();  
  32.         t3.start();  
  33.         t4.start();  
  34.             }  
  35. }  

四、同步代码块与同步函数

       1,同步代码块是将多条操作共享数据的语句放到synchronized(锁对象){}的大括号中,实现同步

       2,同步函数是将同步放在函数上也叫方法,来达到同步,使用方式如下,Bank类为银行具有存钱的功能其内部sum为共享数据且有多条语句操作它,而且语句都需要同步因此把同步放在函数上

[java] view plaincopy
  1. class Bank  
  2. {  
  3.     private int sum;  
  4.     public synchronized void add(int n )  
  5.     {                                                                                                           sum = sum+n;  
  6.         try{Thread.sleep(10);}catch(Exception e){}  
  7.         System.out.println("sum="+sum);  
  8.     }  
  9. }  
  10. class Cash implements Runnable  
  11. {  
  12.     private Bank b = new Bank();  
  13.     public void run()  
  14.     {  
  15.         for(int x=0;x<3;x++)  
  16.         {  
  17.             b.add(100);  
  18.         }  
  19.     }  
  20. }  
  21.   
  22. class CashDemo   
  23. {  
  24.     public static void main(String[] args)   
  25.     {  
  26.         Cash c = new Cash();  
  27.         Thread t1 = new Thread(c);  
  28.         Thread t2 = new Thread(c);  
  29.         t1.start();  
  30.         t2.start();  
  31.   
  32.     }  
  33. }  

五、同步锁

       1,同步函数的锁是this,同步函数使用的锁对象是this

程序通过判断flag标记来决定执行同步函数show或同步代码块,当同步代码块为的锁对象为obj即synchronized(obj)时程序出现负票现象,当同步代码块为的锁对象为this即synchronized(this)时程序运行正常。因此说明同步函数的锁是this。

[java] view plaincopy
  1. class ThisTicket implements Runnable  
  2. {  
  3.     private int ticket = 100;  
  4.     boolean flag = true;  
  5.     Object obj = new Object();  
  6.     public void run()  
  7.     {  
  8.         if(flag)  
  9.         {  
  10.             while(true)  
  11.             {  
  12.                 //synchronized(obj)  //obj锁  
  13.                 synchronized(this//this锁  
  14.                 {  
  15.                     if(ticket>0)  
  16.                     {  
  17.                         try  
  18.                         {  
  19.                             Thread.sleep(10);  
  20.                         }  
  21.                         catch (Exception e)  
  22.                         {  
  23.                         }  
  24.                         System.out.println(Thread.currentThread().getName()+"code sale:"+ticket--);  
  25.                     }  
  26.                 }  
  27.             }  
  28.         }else  
  29.         {  
  30.             while(true)  
  31.             show();  
  32.         }  
  33.           
  34.     }  
  35.     public synchronized void show()//this锁  
  36.     {  
  37.         if(ticket>0)  
  38.         {  
  39.             try  
  40.             {  
  41.                 Thread.sleep(10);  
  42.             }  
  43.             catch (Exception e)  
  44.             {  
  45.             }  
  46.             System.out.println(Thread.currentThread().getName()+"method sale:"+ticket--);  
  47.         }  
  48.     }  
  49. }        

 

[java] view plaincopy
  1. class ThisLockThread  
  2. {  
  3.     public static void main(String[] args)   
  4.     {  
  5.         ThisTicket tt = new ThisTicket();  
  6.         Thread t1 = new Thread(tt);  
  7.         Thread t2 = new Thread(tt);  
  8.         t1.start();  
  9.         try  
  10.         {  
  11.             Thread.sleep(10);  
  12.         }  
  13.         catch (Exception e)  
  14.         {  
  15.         }  
  16.         tt.flag=false;  
  17.         t2.start();   
  18.     }  
  19. }  

 2,静态同步函数的锁是所在类字节码文件

        上例程序中将show声明为static即public static synchronized void show()时,当同步代码块为的锁对象为obj即synchronized(obj)时程序出现负票现象,当同步代码块为的锁对象为this即synchronized(this)时程序出现负票现象。当同步代码块为的锁对象为StaticTicket.class即synchronized(StaticTicket.class)时程序运行正常,因此说明同步函数的锁是StaticTicket.class。因此静态同步函数的锁是所在类字节码文件。

六、总结同步的前提

        1,必须要有两个或以上个线程

        2,必须是多个线程使用同一个锁

七、多线程中生产者与消费者问题

       多线程不同方向操作数据,解决办法 1.,线程只要被唤醒,就必须判断标记。2,唤醒全部的线程

[java] view plaincopy
  1. //定义产品  
  2. class Rescourc  
  3. {  
  4.     private String name ;//产品的名字  
  5.     private int count = 0 ;//产品的计数器  
  6.     private boolean flag = false;  
  7.   
  8.     //提供一个赋值的方法,生产产品  
  9.     public synchronized void set(String name){  
  10.        while(flag==true){//生产完了,没消费呢,不能生产了  
  11.          trythis.wait();}catch(Exception e){}  
  12.        }  
  13.        this.name = name + count;  
  14.        count++;  
  15.        System.out.println(Thread.currentThread().getName()+  
  16.            "生产---"+this.name);  
  17.        //标记改成true  
  18.        flag = true;  
  19.        this.notifyAll();  
  20.          
  21.     }  
  22.     //提供一个获取值的方法,消费产品  
  23.     public synchronized void get(){  
  24.       while(flag==false){//消费完了,没生产了,不能再消费了  
  25.          trythis.wait();}catch(Exception e){}  
  26.       }  
  27.       System.out.println(Thread.currentThread().getName()+  
  28.           "消费======="+this.name);  
  29.       flag = false;  
  30.       this.notifyAll();  
  31.     }  
  32. }  
  33.   
  34. //定义生产者类,实现接口,覆盖run方法  
  35. class Pro implements Runnable  
  36. {  
  37.     private Rescourc r ;  
  38.     Pro(Rescourc r){ this.r = r;}  
  39.     public void run(){  
  40.        while(true){  
  41.            r.set("鼠标");  
  42.        }  
  43.     }  
  44. }  
  45. //定义消费者类,实现接口,覆盖run方法  
  46. class Cus implements Runnable  
  47. {  
  48.     private Rescourc r ;  
  49.     Cus(Rescourc r){ this.r = r;}  
  50.     public void run(){  
  51.          
  52.        while(true){  
  53.           r.get();  
  54.        }  
  55.     }  
  56. }  
  57. class ProCusDemo   
  58. {  
  59.     public static void main(String[] args)   
  60.     {  
  61.         Rescourc r = new Rescourc();//资源产品  
  62.         Pro p = new Pro(r);//生产者  
  63.         Cus c = new Cus(r);//消费者  
  64.   
  65.         Thread t1 = new Thread(p);  
  66.         Thread t2 = new Thread(c);  
  67.         Thread t3 = new Thread(p);  
  68.         Thread t4 = new Thread(c);  
  69.   
  70.         t1.start();  
  71.         t2.start();  
  72.         t3.start();  
  73.         t4.start();  
  74.       
  75.     }  
  76. }  

          程序Rescourc类的run方法使用的是while(flag==true)语句而不是if语句,因为while语句要反复判断标记,而if不是。this.notifyAll()在唤醒线程时使用的是notifyAll()包括唤醒所有线程包括不同操作方的线程,而notify()只唤醒最先wait的线程容易造成所有线程进入wait。
八、停止线程

        1,由于stop方法已经过时,因此停止线程的方法只有一种就是让run方法结束,开启多线程运行,运行的代码通常都是循环结构,因此只要控制循环就可以让run方法结束,也就是线程结束。

[java] view plaincopy
  1. class StopThread implements Runnable  
  2. {  
  3.     private boolean flag=true;  
  4.   
  5.     public void run()  
  6.     {  
  7.         while(flag)  
  8.             System.out.println(Thread.currentThread().getName()+" run");  
  9.     }  
  10.     public void changeFlag()  
  11.     {  
  12.         flag=false;  
  13.     }  
  14. }  
  15. class StopThreadDemo   
  16. {  
  17.     public static void main(String[] args)   
  18.     {  
  19.         StopThread st= new StopThread();  
  20.           
  21.         Thread t1 = new Thread(st);  
  22.         Thread t2 = new Thread(st);  
  23.           
  24.         t1.start();  
  25.         t2.start();  
  26.         int n=0;  
  27.         while(true)  
  28.         {  
  29.             if(n++==60)  
  30.             {  
  31.                 st.changeFlag();  
  32.                 break;  
  33.             }     
  34.             System.out.println(Thread.currentThread().getName()+" run--------"+n);  
  35.         }  
  36.           
  37.               
  38.         System.out.println("over");  
  39.     }  
  40. }  

        主线程执行if语句当n=60时st对象调用changeFlag方法将标记变为false从而使run方法中while语句的标记变为false,使while语句结束循环,结束线程。

        2,当线程进入冻结状态将读不到标记,线程就不会结束,使程序挂起,因此当没有指定方式让线程恢复到运行状态时,就需要对冻结进行清除,强转让线程恢复到运行状态,在对标记进行判断,从而使循环结束,线程结束。

              2.1,Thread类的interrupt()方法解决

[java] view plaincopy
  1. class StopThread implements Runnable  
  2. {  
  3.     private boolean flag=true;  
  4.   
  5.     public synchronized void run()  
  6.     {  
  7.         while(flag)  
  8.             try  
  9.             {  
  10.                 wait();  
  11.             }  
  12.             catch (Exception e)  
  13.             {  
  14.                 System.out.println(Thread.currentThread().getName()+" exception");  
  15.             }  
  16.               
  17.             System.out.println(Thread.currentThread().getName()+" run");  
  18.     }  
  19.     public void changeFlag()  
  20.     {  
  21.         flag=false;  
  22.     }  
  23. }  
  24. class StopThreadDemo   
  25. {  
  26.     public static void main(String[] args)   
  27.     {  
  28.         StopThread st= new StopThread();  
  29.           
  30.         Thread t1 = new Thread(st);  
  31.         Thread t2 = new Thread(st);  
  32.           
  33.         t1.start();  
  34.         t2.start();  
  35.         int n=0;  
  36.         while(true)  
  37.         {  
  38.             if(n++==60)  
  39.             {  
  40.                 st.changeFlag();  
  41.                 t1.interrupt();  
  42.                 t2.interrupt();  
  43.                 break;  
  44.             }     
  45.             System.out.println(Thread.currentThread().getName()+" run--------"+n);  
  46.         }  
  47.           
  48.               
  49.         System.out.println("over");  
  50.     }  
  51. }  

        线程t1和t2执行wait()后进入冻结状态,因而读不到flag,所以程序结束不了,t1和t2在调用interrupt方法后回复到运行状态,执行while语句判断标记为false,结束循环,结束线程。


-------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

0 0
原创粉丝点击