多线程(认识多线程、线程的常用操作方法)

认识多线程

进程与线程:

进程是程序的一次动态执行过程,它经历了从代码加载、到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。

多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。

Java的多线程实现,有一下两种方式:

·继承Thread类

·实现Runnable接口

Thread类

Thread类是在java.lang包下定义的,一个类只要继承了Thread类,此类就称为多线程操作类。在Thread子类中,必须明确的腹泻Thread中的run()方法,此方法称为线程的主体。

·多线程的定义语法:

  1. class 类名称 extends Thread {  //继承Thread类  
  2.     属性...;                  //类中定义属性  
  3.     方法...;                  //类中定义方法  
  4.     //覆写Thread中的run()方法,此方法是线程的主体  
  5.     public void run(){  
  6.         线程主体;  
  7.     }  
  8. }  

如以下的一个类中就具备了多线程的操作功能:

  1. class MyThread extends Thread   //继承自Thread类  
  2. {  
  3.     private String name;    //表示线程的名称  
  4.     public MyThread(String name){     
  5.         this.name=name; //通过构造方法配置name属性  
  6.     }  
  7.     public void run(){  //覆写run方法,作为线程的主体  
  8.         for(int i=0;i<10;i++){  
  9.             System.out.println(name+"运行,i="+i);  
  10.         }  
  11.     }  
  12. }  

实例化该类以后,通过对象可以调用run方法:

  1. public class ThreadDemo01   
  2. {  
  3.     public static void main(String args[]){  
  4.         MyThread m=new MyThread("A");   //实例化MyThread类  
  5.         MyThread m1=new MyThread("B");  
  6.         m.run();   //调用run 方法  
  7.         m1.run();  
  8.     }  
  9. }  

同过程序的运行结果可以发现,程序是先执行A后再执行B的,并没有达到所谓的并发执行的效果,如果是想要启动一个线程必须要使用Thread类中定义的start()方法。

  1. public class ThreadDemo01   
  2. {  
  3.     public static void main(String args[]){  
  4.         MyThread m=new MyThread("A");  //实例化MyThread类  
  5.         MyThread m1=new MyThread("B");  
  6.         m.start();   //调用Thread类中定义的start启动一个线程  
  7.         m1. start ();  
  8.     }  
  9. }  

注意:如果当前线程已经启动了(就是说已经调用了start方法),再次调用start方法就会出现java.lang.IllegalThreadStateException异常

Runnable接口

在Java中也可以通过实现Runnable接口的方式实现多线程,Runnable接口中只定义了一个抽象方法:

·public void run();

通过Runnable接口实现多线程:

  1. class 类名称 implements Runnable   //实现Runnable接口  
  2. {  
  3.     属性...;                      //类中定义属性  
  4.     方法...;                      //类中定义方法  
  5.     public void run(){          //覆写Runnable接口中的run方法  
  6.         线程主体;  
  7.     }  
  8. }  

如以下代码:

  1. class MyThread implements Runnable  //继承自Thread类  
  2. {  
  3.     private String name;    //表示线程的名称  
  4.     public MyThread(String name){     
  5.         this.name=name; //通过构造方法配置name属性  
  6.     }  
  7.     public void run(){  //覆写run方法,作为线程的主体  
  8.         for(int i=0;i<10;i++){  
  9.             System.out.println(name+"运行,i="+i);  
  10.         }  
  11.     }  
  12. }  
  13. public class ThreadDemo02  
  14. {  
  15.     public static void main(String args[]){  
  16.         MyThread m1=new MyThread("A");  //实例化对象  
  17.         MyThread m2=new MyThread("B");  
  18.         Thread t1=new Thread(m1);   //实例化Thread类  
  19.         Thread t2=new Thread(m2);  
  20.         t1.start(); //启动线程  
  21.         t2.start();   
  22.     }  
  23. }  

以上代码通过实现Runnable接口实现了多线程,但是要注意的是,一个线程启动的方法是在Thread类中的start()方法,此时Runnable接口中是没有该方法的,所以要实例化Thread类,调用其start方法。

Runnable接口与Thread类的区别

使用Thread类在操作多线程的时候无法达到资源共享的目的,而使用Runnable接口实现的多线程操作可以实现资源共享。如以下的代码:

实现Runnable接口:

  1. class MyThread implements Runnable  //继承自Thread类  
  2. {  
  3.     private int Number1=5;  //表示线程的名称  
  4.   
  5.     public void run(){  //覆写run方法,作为线程的主体  
  6.       
  7.                     for(int i=0;i<100;i++){  
  8.                         if(Number1>0)  
  9.                     System.out.println("ticket"+Number1--);  
  10.                 }  
  11.     }  
  12. }  
  13. public class ThreadDemo02  
  14. {  
  15.     public static void main(String args[]){  
  16.         MyThread m=new MyThread();  
  17.         new Thread(m).start();  
  18.         new Thread(m).start();  
  19.           
  20.     }  
  21. }  

此时可以发现启动了两个线程,但是票的总数是5。

继承自Thread类:

  1. class MyThread extends Thread   //继承自Thread类  
  2. {  
  3.     private int Number1=5;  //表示线程的名称  
  4.   
  5.     public void run(){  //覆写run方法,作为线程的主体  
  6.       
  7.                     for(int i=0;i<100;i++){  
  8.                         if(Number1>0)  
  9.                     System.out.println("ticket"+Number1--);  
  10.                 }  
  11.     }  
  12. }  
  13. public class ThreadDemo03  
  14. {  
  15.     public static void main(String args[]){  
  16.         MyThread m1=new MyThread();  
  17.         MyThread m2=new MyThread();  
  18.         MyThread m3=new MyThread();  
  19.         m1.start();     //启动线程  
  20.         m2.start();     //启动线程    
  21.         m3.start();     //启动线程  
  22.     }  
  23. }  

此时启动了三个线程,票的总数就是15,因为在每一个MyThread对象中都包含各自的ticket属性。

Thread类与Runnable接口的使用结论

实现Runnable接口比继承Thread类有如下明显的优点:

·适合多个相同程序代码的线程去处理同一个资源。

·可以避免由于单继承局限所带来的影响。

·增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。

综合以上来看:开发中使用Runnable接口是比较合适的,应该充分使用其以上特性。

线程的状态

多线程在操作中也是有一个固定的操作状态的:

·创建对象:准备好了一个多线程的对象: Thread t=new Thread();

·就绪状态:调用了start()方法,等待CPU进行调度。

·运行状态:执行了run()方法。

·阻塞状态:暂时停止执行,可能将资源交给其它资源使用。

·终止状态(死亡状态):线程执行完毕,不再进行使用。

线程的常用操作方法

取得当前线程的名称

程序可以通过currentThread()方法取得当前正在运行的线程的名称。代码如下:

  1. class MyThread implements Runnable  //实现Runnable接口  
  2. {  
  3.     public void run(){  //覆写run 方法  
  4.         System.out.println(Thread.currentThread().getName()+"运行");  
  5.     }  
  6. }  
  7. public class currentThreadDemo  
  8. {  
  9.     public static void main(String args[]){  
  10.         MyThread m=new MyThread();  //实例化Runnable子类对象  
  11.         new Thread(m).start();  //让JVM为线程设置名称并启动  
  12.         new Thread(m,"线程").start(); //为线程设置名称并启动线程  
  13.         m.run();    //直接调用run方法 获得当前main方法线程名称  
  14.         //程序输出  
  15.         //Thread-0运行  
  16.         //main运行  
  17.         //线程运行  
  18.     }  
  19. }  

由以上代码可以得知:如果不为一个线程设置名称,则JVN自动按Thread-0、Thread-1、Thread-2、….的格式为线程设置名称。注意:main方法的线程名称为main

扩展:既然主方法都是以线程的形式出现的,那么JAVA运行时至少应该是启动了两个线程。每当JAVA程序运行的时候,实际上都启动了一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程,java本身具有垃圾收集机制,所以Java运行时至少启动了两个线程:

主线程、GC(垃圾回收的线程)

判断线程是否启动

判断线程是否启动使用isAlive()方法,返回boolean得到当前线程是否活动。

  1. class MyThread implements Runnable  //实现Runnable接口  
  2. {  
  3.     public void run(){  //覆写run 方法  
  4.         System.out.println(Thread.currentThread().getName()+"运行");  
  5.     }  
  6. }  
  7. public class ThreadAliveDemo  
  8. {  
  9.     public static void main(String args[])throws IllegalThreadStateException{  
  10.         MyThread m=new MyThread();  //实例化Runnable子类对象  
  11.         Thread t=new Thread(m,"自定义线程");  
  12.         System.out.println("线程执行前:"+t.isAlive());       //false  
  13.         t.start();  
  14.         System.out.println("线程启动之后:"+t.isAlive());  //true  
  15.     }  
  16. }  

线程的强制运行

在线程操作中,可以使用join()方法让一个线程强制运行,线程强制运行期间,其它线程无法运行,必须等待此线程完成之后才可以继续执行。

  1. class MyThread implements Runnable  //实现Runnable接口  
  2. {  
  3.     public void run(){  //覆写run 方法  
  4.         for(int i=0;i<50;i++){  
  5.             System.out.println(Thread.currentThread().getName()+"运行,i="+i);  
  6.         }  
  7.           
  8.     }  
  9. }  
  10. public class ThreadJoinDemo  
  11. {  
  12.     public static void main(String args[]){  
  13.         MyThread m=new MyThread();  //实例化Runnable子类对象  
  14.         Thread t=new Thread(m,"自定义线程");  
  15.         t.start();  
  16.         for(int i=0;i<50;i++){  
  17.             if(i>10)  
  18.                 try{  
  19.                 //join方法会出现异常  
  20.                 t.join();  
  21.             }catch(InterruptedException e){  
  22.             }  
  23.             System.out.println("main线程运行,i="+i);  
  24.         }  
  25.     }  
  26. }  

以上代码中,程序一开始自定义跟main线程会交替运行,但是当main线程中i+10时,就会强制的先执行自定义线程,待其执行完以后,再执行main线程,这就是线程的强制运行。

线程的休眠

在程序中允许一个线程进行暂时的休眠,直接使用Thread.sleep()方法即可。

  1. class MyThread implements Runnable  //实现Runnable接口  
  2. {  
  3.     public void run(){  //覆写run 方法  
  4.         for(int i=0;i<50;i++){  
  5.             try{  
  6.                 //sleep方法会出现异常  
  7.                 Thread.sleep(1000);     //程序会暂停1000毫秒再执行  
  8.             }catch(InterruptedException e){  
  9.             }  
  10.             System.out.println(Thread.currentThread().getName()+"运行,i="+i);  
  11.         }  
  12.     }  
  13. }  
  14. public class ThreadSleepDemo  
  15. {  
  16.     public static void main(String args[]){  
  17.         MyThread m=new MyThread();  //实例化Runnable子类对象  
  18.         Thread t=new Thread(m,"自定义线程");  
  19.         t.start();  
  20.     }  
  21. }  

以上的程序会间隔1秒钟执行一次。Sleep()方法就时暂时的休眠,指定了一定了的时间后程序会继续执行。

线程的中断

一个线程可以被另一个线程中断其操作的状态,使用interrupt()方法。

  1. class MyThread implements Runnable  //实现Runnable接口  
  2. {  
  3.     public void run(){  //覆写run 方法  
  4.           
  5.             try{  
  6.                 //sleep方法会出现异常  
  7.                 System.out.println("1、进入run方法");  
  8.                 Thread.sleep(2000);     //程序会暂停1000毫秒再执行  
  9.                 System.out.println("2、已经完成了休眠");  
  10.             }catch(InterruptedException e){  
  11.                 System.out.println("3、休眠被终止!");  
  12.                 return;     //返回方法调用处  
  13.               
  14.               
  15.         }  
  16.         System.out.println("4、run方法正常结束");  
  17.     }  
  18. }  
  19. public class ThreadInterruptDemo  
  20. {  
  21.     public static void main(String args[]){  
  22.         MyThread m=new MyThread();  //实例化Runnable子类对象  
  23.         Thread t=new Thread(m,"自定义线程");  
  24.         t.start();  
  25.             try{  
  26.                 //sleep方法会出现异常  
  27.                 Thread.sleep(10000);  
  28.             }catch(InterruptedException e){  
  29.             }  
  30.           
  31.         t.interrupt();  
  32.     }  
  33. }  

后台线程

在Java程序中,只要前台有一个线程在运行,则整个Java进程都不会消失,所以此时可以设置一个后台线程,这样即使Java进程结束了,此后台线程依然会执行。要想实现这样的操作,直接使用setDaemon()方法即可。

  1. class MyThread implements Runnable{ // 实现Runnable接口  
  2.     public void run(){  // 覆写run()方法  
  3.         while(true){  
  4.             System.out.println(Thread.currentThread().getName() + "在运行。") ;  
  5.         }  
  6.     }  
  7. };  
  8. public class ThreadDaemonDemo{  
  9.     public static void main(String args[]){  
  10.         MyThread mt = new MyThread() ;  // 实例化Runnable子类对象  
  11.         Thread t = new Thread(mt,"线程");     // 实例化Thread对象  
  12.         t.setDaemon(true) ; // 此线程在后台运行  
  13.         t.start() ; // 启动线程  
  14.     }  
  15. };  

此方法了解即可,不重要。

线程的优先级

优先级越高的线程,被执行的顺序就比较靠前,在Thread中存在三个常量:MAX_PRIORITY、

MIN_PRIORITY、NORM_PRIORITY执行顺序为:MAX_PRIORITY> NORM_PRIORITY>

MIN_PRIORITY、代码如下:

  1. class MyThread implements Runnable  //实现Runnable接口  
  2. {  
  3.     public void run(){    
  4.             System.out.println(Thread.currentThread().getName());  
  5.     }  
  6. }  
  7. public class ThreadPriorDemo  
  8. {  
  9.     public static void main(String args[]){  
  10.         Thread t1=new Thread(new MyThread(),"线程A");  
  11.         Thread t2=new Thread(new MyThread(),"线程B");  
  12.         Thread t3=new Thread(new MyThread(),"线程C");  
  13.         t1.setPriority(Thread.MAX_PRIORITY);  
  14.         t2.setPriority(Thread.NORM_PRIORITY);  
  15.         t3.setPriority(Thread.MIN_PRIORITY);  
  16.         t1.start();  
  17.         t2.start();  
  18.         t3.start();  
  19.     }  
  20. }  

实际上:MAX_PRIORITY=10、 NORM_PRIORITY=5、MIN_PRIORITY=1

同步与死锁

先看如以下的代码:

  1. class MyThread implements Runnable{  
  2.     int ticket=5;   //假设一共有5张票  
  3.     public void run(){    
  4.         for(int i=0;i<50;i++){  
  5.             if(ticket>0){     //还有票的话就继续卖  
  6.                 try{  
  7.                     Thread.sleep(500);  //为了保证正确,加入延迟  
  8.                 }catch(InterruptedException e){  
  9.                     e.printStackTrace();  
  10.                 }  
  11.                 System.out.println("卖票:ticeket="+ticket--);  
  12.             }  
  13.         }  
  14.     }  
  15. }  
  16. public class synchronizedDemo02  
  17. {  
  18.     public static void main(String args[]){  
  19.         MyThread m1=new MyThread(); //定义线程对象  
  20.         new Thread(m1).start();     //定义Thread对象  
  21.         new Thread(m1).start();  
  22.         new Thread(m1).start();  
  23.     }  
  24. }  

代码最终结果如下:

程序的问题:从运行结果中可以发现,程序中加入了延迟操作以后,票数会变为负数,出现这种情况的原因是:一个线程有可能在还没有对票数进行减操作之前,其它线程已经将票数减少了,这样一来就出现了票数为负的情况。

问题解决:如果想解决这种问题,就必须使用线程的同步操作,所谓的同步就是指多个操作在同一个时间段内只能有一个线程进行,其它线程要等待此线程完成之后才可以执行。

使用同步

要想解决资源共享的同步操作问题,可以使用同步代码块或者是同步方法两种方式完成。

同步代码块:

之前介绍了代码块分为四种:

1、  普通代码块:是直接定义在方法之中的。

2、  构造块:直接定义在类中,优先于构造方法执行,重复调用。

3、  静态块:使用static关键字声明:优先于构造块执行,只执行一次。

4、  同步代码块:使用synchronized关键字声明的代码块,成为同步代码块。

同步代码块格式:

  1. Synchronized(同步对象){  
  2.     需要同步的代码;  
  3. }  

同步代码块必须指明同步的对象,一般情况下会将当前对象进行同步,使用this表示。

使用同步后的代码:

  1. class MyThread implements Runnable{  
  2.     int ticket=5;  
  3.     public void run(){    
  4.         synchronized(this){     //同步代码块,同步当前对象  
  5.         for(int i=0;i<50;i++){  
  6.             if(ticket>0){  
  7.                     try{  
  8.                         Thread.sleep(300);  
  9.                     }catch(InterruptedException e){  
  10.                         e.getStackTrace();  
  11.                     }  
  12.                     System.out.println("卖票"+(ticket--));      
  13.                 }  
  14.             }  
  15.         }  
  16.     }  
  17. }  
  18. public class synchronizedDemo02  
  19. {  
  20.     public static void main(String args[]){  
  21.         MyThread m1=new MyThread();  
  22.         new Thread(m1).start();  
  23.         new Thread(m1).start();  
  24.         new Thread(m1).start();  
  25.     }  
  26. }  

因为使用了方法的同步之后,在同一时间段内只能又一个线程执行,所以程序的执行效率会明显降低很多。

同步方法

除了可以将需要同步的代码设置成同步代码块之外,也可以使用synchronized关键字将一个方法声明成同步方法。

同步方法定义格式:

  1. snchronized 方法返回值 方法名称(参数列表){};  

使用同步方法以后的代码:

  1. class MyThread implements Runnable{  
  2.     int ticket=5;  
  3.     public void run(){    
  4.         for(int i=0;i<50;i++){  
  5.         this.sale();    //调用当前类中的同步方法  
  6.         }  
  7.     }  
  8.     public synchronized void sale(){    //同步方法  
  9.     if(ticket>0){  
  10.             try{  
  11.                 Thread.sleep(300);  
  12.             }catch(InterruptedException e){  
  13.                 e.getStackTrace();  
  14.             }  
  15.             System.out.println("卖票"+(ticket--));      
  16.         }  
  17.     }  
  18. }  
  19. public class synchronizedDemo03  
  20. {  
  21.     public static void main(String args[]){  
  22.         MyThread m1=new MyThread();  
  23.         new Thread(m1).start();  
  24.         new Thread(m1).start();  
  25.         new Thread(m1).start();  
  26.     }  
  27. }  

死锁

1、  共享时需要进行同步操作

2、  程序中过多的同步会产生死锁。

扩展

方法定义的完整格式:

  1. 访问权限{public、default、protected、private} [final、static、synchronized] 返回值类型 方法名称(参数类型 参数名称,…..) throws Excption1,Exception2{  
  2.    rturn 返回值(没有返回值则表示返回调用处);  
  3. }  

Object类对线程的支持---等待和唤醒

Object类是所有类的父类,在此类中又一下几个方法是对线程操作有所支持的。

线程的唤醒又两个方法:notify、notifyAll。一般来说,所有等待的线程会按照顺序进行排列,如果现在采用notify()方法,则会唤醒第一个等待的线程执行,而如果使用了notifyAll()方法,则会唤醒所有正在等待状态中得线程,哪个线程的优先级高,则就有可能执行。

线程的生命周期

一个新的线程被创建之后,通过start()方法进入到运行状态,在运行状态中可以使用yield()方法礼让,但是仍然可以进行,如果现在一个线程需要暂停,可是使用suspend()、sleep()、wait(),如果现在线程不需要再执行,则可以通过stop方法结束(如果run方法执行完毕也表示线程的结束),或者一个新的线程直接调用stop()方法也可以进行结束。

·suspend()方法:暂时挂起线程。

·resume()方法:恢复挂起的线程

·stop()方法:停止线程

因为以上方法会产生死锁的问题,所以很少使用,如果想停止一个线程,则可以使用一下委婉的方式:

  1. class MyThread implements Runnable{  
  2.     private boolean flag = true ;   // 定义标志位  
  3.     public void run(){  
  4.         int i = 0 ;  
  5.         while(this.flag){  
  6.             System.out.println(Thread.currentThread().getName()  
  7.                 +"运行,i = " + (i++)) ;  
  8.         }  
  9.     }  
  10.     public void stop(){  
  11.         this.flag = false ; // 修改标志位  
  12.     }  
  13. };  
  14. public class StopDemo{  
  15.     public static void main(String args[]){  
  16.         MyThread my = new MyThread() ;  
  17.         Thread t = new Thread(my,"线程") ;    // 建立线程对象  
  18.         t.start() ; // 启动线程  
  19.         try{  
  20.             Thread.sleep(30) ;  
  21.         }catch(Exception e){  
  22.               
  23.         }  
  24.         my.stop() ; // 修改标志位,停止运行  
  25.     }  
  26. };  
原文地址:https://www.cnblogs.com/lingyi1111/p/4433283.html