多线程

多线程
一、多线程的概述

      要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。

1、 进程

        是一个正在执行的程序

2、线程

         就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。

        一个进程中至少有一个线程。

3、多线程

        java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。

4、多线程存在的意义

        多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。

5、计算机CPU的运行原理

         我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。

二、创建线程的方式

               创建线程共有两种方式:继承方式和实现方式(简单的说)。

        1、 继承方式

       通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继        承Thread类,然后复写其run方法的方式来创建线程。

      创建步骤:

        a,定义类继承Thread

        b,复写Thread中的run方法。

             目的:将自定义代码存储在run方法中,让线程运行。

        c,创建定义类的实例对象。相当于创建一个线程。

        d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。

      注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。

      覆盖run方法的原因:

        Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。

      继承方式代码:

[java]  view plain copy
  1. /*  
  2. 创建两线程,和主线程交替运行。  
  3. */    
  4.     
  5. //创建线程Test    
  6. class Test extends Thread    
  7. {    
  8.     private String name;    
  9.     Test(String name)    
  10.     {    
  11.         super(name);    
  12.     }    
  13.     //复写run方法    
  14.     public void run()    
  15.     {    
  16.         for(int x=0;x<60;x++)    
  17.         System.out.println(Thread.currentThread().getName()+"..run..."+x);    
  18.     }    
  19. }    
  20.     
  21. class  ThreadTest    
  22. {    
  23.     public static void main(String[] args)     
  24.     {    
  25.         new Test("one+++").start();//开启一个线程    
  26.     
  27.         new Test("tow———").start();//开启第二线程    
  28.     
  29.         //主线程执行的代码    
  30.         for(int x=0;x<170;x++)    
  31.         System.out.println("Hello World!");    
  32.     }    
  33. }    


        2、 实现方式

        使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复习其中run方法的方式。

创建步骤:

        a,定义类实现Runnable的接口。

        b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。

        c,通过Thread类创建线程对象。

        d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法。

      实现方式代码:

[java]  view plain copy
  1. /* 
  2. 需求:简单的卖票程序。 
  3. 多个窗口卖票。 
  4. */  
  5. class Ticket implements Runnable//extends Thread  
  6. {  
  7.     private  int tick = 100;  
  8.     public void run()  
  9.     {  
  10.         while(true)  
  11.         {  
  12.             if(tick>0)  
  13.             {  
  14.                 //显示线程名及余票数  
  15.                 System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  
  16.             }  
  17.         }  
  18.     }  
  19. }  
  20.    
  21.    
  22. class  TicketDemo  
  23. {  
  24.     public static void main(String[] args)   
  25.     {  
  26.         //创建Runnable接口子类的实例对象  
  27.         Ticket t = new Ticket();  
  28.    
  29.         //有多个窗口在同时卖票,这里用四个线程表示  
  30.         Thread t1 = new Thread(t);//创建了一个线程  
  31.         Thread t2 = new Thread(t);  
  32.         Thread t3 = new Thread(t);  
  33.         Thread t4 = new Thread(t);  
  34.    
  35.         t1.start();//启动线程  
  36.         t2.start();  
  37.         t3.start();  
  38.         t4.start();  
  39.     }  
  40. }  


三、线程安全问题

1、导致安全问题的出现的原因:

        当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。

2、解决办法——同步

        对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

        在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)

        这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。

         a、同步代码块

        用法:

                  synchronized(对象)

                  {需要被同步的代码}

       同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

       同步代码块的代码如下:

[java]  view plain copy
  1. /*    
  2. 给卖票程序示例加上同步代码块。  
  3. */    
  4. class Ticket implements Runnable    
  5. {    
  6.     private int tick=100;    
  7.     Object obj = new Object();    
  8.     public void run()    
  9.     {    
  10.         while(true)    
  11.         {    
  12.             //给程序加同步,即锁    
  13.             synchronized(obj)    
  14.             {    
  15.                 if(tick>0)    
  16.                 {    
  17.                     try    
  18.                     {       
  19.                         //使用线程中的sleep方法,模拟线程出现的安全问题    
  20.                         //因为sleep方法有异常声明,所以这里要对其进行处理    
  21.                         Thread.sleep(10);    
  22.                     }    
  23.                     catch (Exception e)    
  24.                     {    
  25.                     }    
  26.                     //显示线程名及余票数    
  27.                     System.out.println(Thread.currentThread().getName()+"..tick="+tick--);    
  28.                 }    
  29.             }       
  30.         }    
  31.     }    
  32. }    

     

        b,同步函数

        格式:

                在函数上加上synchronized修饰符即可。

       那么同步函数用的是哪一个锁呢?

       函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this

       同步函数代码如下:

[java]  view plain copy
  1. class Ticket implements Runnable    
  2. {    
  3.     private int tick=100;    
  4.     Object obj = new Object();    
  5.     public void run()    
  6.     {    
  7.         while(true)    
  8.         {    
  9.             show();    
  10.         }    
  11.     }    
  12.   //直接在函数上用synchronized修饰即可实现同步    
  13. public synchronized void show()    
  14. {    
  15.         if(tick>0)    
  16.         {    
  17.         try    
  18.         {       
  19.             //使用线程中的sleep方法,模拟线程出现的安全问题    
  20.             //因为sleep方法有异常声明,所以这里要对其进行处理    
  21.             Thread.sleep(10);    
  22.         }    
  23.         catch (Exception e)    
  24.         {    
  25.         }    
  26.         //显示线程名及余票数    
  27.         System.out.println(Thread.currentThread().getName()+"..tick="+tick--);    
  28.     }    
  29. }       
  30. }    

        

       3、同步的前提

             a,必须要有两个或者两个以上的线程。

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

       4、同步的利弊

            好处:解决了多线程的安全问题。

            弊端:多个线程需要判断锁,较为消耗资源。

四、静态函数的同步方式

        如果同步函数被静态修饰后,使用的锁是什么呢?

        通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:

        类名.class 该对象的类型是Class

        这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

        静态函数同步方式代码如下:

[java]  view plain copy
  1. /*  
  2. 加同步的单例设计模式————懒汉式  
  3. */    
  4. class Single    
  5. {    
  6.     private static Single s = null;    
  7.     private Single(){}    
  8.     public static void getInstance()    
  9.     {    
  10.         if(s==null)    
  11.         {    
  12.             synchronized(Single.class)    
  13.             {    
  14.                 if(s==null)    
  15.                     s = new Single();    
  16.             }    
  17.         }    
  18.         return s;    
  19.     }    
  20. }    

五、死锁

        当同步中嵌套同步时,就有可能出现死锁现象。

       死锁的代码如下:

[java]  view plain copy
  1. /* 
  2. 写一个死锁程序 
  3. */  
  4.    
  5. //定义一个类来实现Runnable,并复写run方法  
  6. class LockTest implements Runnable  
  7. {  
  8.     private boolean flag;  
  9.     LockTest(boolean flag)  
  10.     {  
  11.         this.flag=flag;  
  12.     }  
  13.     public void run()  
  14.     {  
  15.         if(flag)  
  16.         {  
  17.             while(true)  
  18.             {  
  19.                 synchronized(LockClass.locka)//a锁  
  20.                 {  
  21.                     System.out.println(Thread.currentThread().getName()+"------if_locka");  
  22.    
  23.                     synchronized(LockClass.lockb)//b锁  
  24.                     {  
  25.                     System.out.println(Thread.currentThread().getName()+"------if_lockb");  
  26.                     }  
  27.                 }  
  28.             }  
  29.         }  
  30.         else  
  31.         {  
  32.             while(true)  
  33.             {  
  34.                 synchronized(LockClass.lockb)//b锁  
  35.                 {  
  36.                   System.out.println(Thread.currentThread().getName()+"------else_lockb");  
  37.    
  38.                     synchronized(LockClass.locka)//a锁  
  39.                     {  
  40.                    System.out.println(Thread.currentThread().getName()+"------else_locka");  
  41.                     }  
  42.                 }  
  43.             }  
  44.         }  
  45.     }  
  46. }  
  47.    
  48. //定义两个锁  
  49. class LockClass  
  50. {  
  51.     static Object locka = new Object();  
  52.     static Object lockb = new Object();  
  53. }  
  54.    
  55. class DeadLock  
  56. {  
  57.     public static void main(String[] args)  
  58.     {  
  59.         //创建2个进程,并启动  
  60.         new Thread(new LockTest(true)).start();  
  61.         new Thread(new LockTest(false)).start();  
  62.     }  
  63. }  

六、线程间通信

        其实就是多个线程在操作同一个资源,但是操作的动作不同。

        使用同步操作同一资源的代码:

[java]  view plain copy
  1. /* 
  2.     有一个资源 
  3. 一个线程往里存东西,如果里边没有的话 
  4. 一个线程往里取东西,如果里面有得话 
  5. */  
  6.    
  7. //资源  
  8. class Resource  
  9. {  
  10.     private String name;  
  11.     private String sex;  
  12.     private boolean flag=false;  
  13.       
  14.     public synchronized void setInput(String name,String sex)  
  15.     {  
  16.         if(flag)  
  17.         {  
  18.             try{wait();}catch(Exception e){}//如果有资源时,等待资源取出  
  19.         }  
  20.         this.name=name;  
  21.         this.sex=sex;  
  22.    
  23.         flag=true;//表示有资源  
  24.         notify();//唤醒等待  
  25.     }  
  26.    
  27.     public synchronized void getOutput()  
  28.     {         
  29.         if(!flag)  
  30.         {  
  31.             try{wait();}catch(Exception e){}//如果木有资源,等待存入资源  
  32.         }  
  33.         System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出  
  34.                   
  35.         flag=false;//资源已取出  
  36.         notify();//唤醒等待  
  37.     }  
  38. }  
  39.    
  40.    
  41. //存线程  
  42. class Input implements Runnable  
  43. {  
  44.     private Resource r;  
  45.     Input(Resource r)  
  46.     {  
  47.         this.r=r;  
  48.     }  
  49.     public void run()//复写run方法  
  50.     {  
  51.         int x=0;  
  52.         while(true)  
  53.         {  
  54.             if(x==0)//交替打印张三和王羲之  
  55.             {  
  56.                 r.setInput("张三",".....man");  
  57.             }  
  58.             else  
  59.             {  
  60.                 r.setInput("王羲之","..woman");  
  61.             }  
  62.             x=(x+1)%2;//控制交替打印  
  63.         }  
  64.     }  
  65. }  
  66.    
  67. //取线程  
  68. class Output implements Runnable  
  69. {  
  70.     private Resource r;  
  71.     Output(Resource r)  
  72.     {  
  73.         this.r=r;  
  74.     }  
  75.     public void run()//复写run方法  
  76.     {  
  77.         while(true)  
  78.         {  
  79.             r.getOutput();  
  80.         }  
  81.     }  
  82. }  
  83.    
  84.    
  85.    
  86. class ResourceDemo2   
  87. {  
  88.     public static void main(String[] args)   
  89.     {  
  90.         Resource r = new Resource();//表示操作的是同一个资源  
  91.    
  92.         new Thread(new Input(r)).start();//开启存线程  
  93.    
  94.         new Thread(new Output(r)).start();//开启取线程  
  95.     }  
  96. }  

七、停止线程

       只有一种办法,那就是让run方法结束。

       那怎么样让run方法结束呢

       开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

      停止线程,要设置一个flag标记,代码如下:

[java]  view plain copy
  1. public  void run()    
  2. {    
  3.     while(flag)    
  4.     {       
  5.         System.out.println(Thread.currentThread().getName()+"....run");    
  6.     }    
  7. }    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值