java 多线程 目录:
在这篇文章里,我们关注多线程。多线程是一个复杂的话题,包含了很多内容,这篇文章主要关注线程的基本属性、如何创建线程、线程的状态切换以及线程通信等。
线程是操作系统运行的基本单位,它被封装在进程中,一个进程可以包含多个线程。即使我们不手动创造线程,进程也会有一个默认的线程在运行。
对于JVM来说,当我们编写一个单线程的程序去运行时,JVM中也是有至少两个线程在运行,一个是我们创建的程序,一个是垃圾回收。
一、 线程基本信息
我们可以通过Thread.currentThread()方法获取当前线程的一些信息,并对其进行修改。
我们来看以下代码:
1 public class test_1 { 2 3 public static void main(String[] args){ 4 //查看并修改当前线程的属性 5 String name = Thread.currentThread().getName(); 6 int priority = Thread.currentThread().getPriority(); 7 String groupName = Thread.currentThread().getThreadGroup().getName(); 8 //该线程是否为守护线程。 9 boolean isDaemon = Thread.currentThread().isDaemon(); 10 //测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。 11 boolean isAlive = Thread.currentThread().isAlive(); 12 System.out.println("Thread Name:"+name); 13 System.out.println("poiority:"+priority); 14 System.out.println("groupName:"+groupName); 15 System.out.println("isDaemon:"+isDaemon); 16 System.out.println("isAlive:"+isAlive); 17 System.out.println("-------------"); 18 Thread.currentThread().setName("Test_修改后名字"); 19 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 20 name = Thread.currentThread().getName(); 21 priority = Thread.currentThread().getPriority(); 22 groupName = Thread.currentThread().getThreadGroup().getName(); 23 isDaemon = Thread.currentThread().isDaemon(); 24 System.out.println("Thread Name:" + name); 25 System.out.println("Priority:" + priority); 26 27 } 28 29 30 }
执行结果:
Thread Name:main
poiority:5
groupName:main
isDaemon:false
isAlive:true
-------------
Thread Name:Test_修改后名字
Priority:10
其中列出的属性说明如下:
- GroupName,每个线程都会默认在一个线程组里,我们也可以显式的创建线程组,一个线程组中也可以包含子线程组,这样线程和线程组,就构成了一个树状结构。
- Name,每个线程都会有一个名字,如果不显式指定,那么名字的规则是“Thread-xxx”。
- Priority,每个线程都会有自己的优先级,JVM对优先级的处理方式是“抢占式”的。当JVM发现优先级高的线程时,马上运行该线程;对于多个优先级相等的线程,JVM对其进行轮询处理。Java的线程优先级从1到10,默认是5,Thread类定义了2个常量:MIN_PRIORITY和MAX_PRIORITY来表示最高和最低优先级。
我们可以看下面的代码,它定义了两个不同优先级的线程:
1 public class test_2 { 2 3 public static void main(String[] args) 4 { 5 Thread thread1 = new Thread("low") 6 { 7 public void run(){ 8 for(int i=0;i<5;i++){ 9 System.out.println(this.getName() +" is running!"); 10 } 11 } 12 }; 13 14 Thread thread2 = new Thread("high"){ 15 public void run(){ 16 for(int i=0;i<5;i++){ 17 System.out.println(this.getName() +" is running!"); 18 } 19 } 20 }; 21 thread1.setPriority(Thread.MIN_PRIORITY); 22 thread2.setPriority(Thread.MAX_PRIORITY); 23 thread1.start(); 24 thread2.start(); 25 } 26 27 }
执行结果:
low is running!
low is running!
low is running!
low is running!
low is running!
high is running!
high is running!
high is running!
high is running!
high is running!
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~·0之间,也可以使用Thread类提供的三个静态常量:
MAX_PRIORITY =10
MIN_PRIORITY =1
NORM_PRIORITY =5
- isDaemon,这个属性用来控制父子线程的关系,如果设置为true,当父线程结束后,其下所有子线程也结束,反之,子线程的生命周期不受父线程影响。
我们来看下面的例子:
1 public class test_3 { 2 3 public static void main(String[] args) 4 { 5 Thread thread = new Thread("daemon") 6 { 7 public void run() 8 { 9 Thread subThread = new Thread("sub") 10 { 11 public void run() 12 { 13 for(int i=0;i<100;i++) 14 { 15 System.out.println(this.getName()+" is running! "+i); 16 } 17 } 18 }; 19 //对比注释该语句 20 subThread.setDaemon(true); 21 System.out.println("subThread isDaemon:"+subThread.isDaemon()); 22 subThread.start(); 23 System.out.println("main thread is end!"); 24 25 } 26 }; 27 thread.start(); 28 } 29 30 }
上面代码的运行结果,对比注释掉语句//subThread.setDaemon(true);
如果 subThread isDaemon:false, 子进程会全部执行完
如果 subThread isDaemon:true, 子进程很快就结束了
(一次)执行结果:每次会不同
subThread isDaemon:true
main thread is end!
sub is running! 0
sub is running! 1
sub is running! 2
sub is running! 3
sub is running! 4
sub is running! 5
sub is running! 6
sub is running! 7
扩展:
JAVA并发编程——守护线程(Daemon Thread)
在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。
所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。下面的例子也说明了这个问题
1 public class test_4 { 2 public static void main(String[] args) { 3 Thread t = new Thread(new ADaemon()); 4 //测试该语句注释后 5 t.setDaemon(true); 6 t.start(); 7 } 8 9 } 10 11 class ADaemon implements Runnable 12 { 13 public void run() { 14 System.out.println("start ADaemon..."); 15 try { 16 TimeUnit.SECONDS.sleep(1); 17 } catch (InterruptedException e) { 18 System.out.println("Exiting via InterruptedException"); 19 } 20 finally{ 21 System.out.println("finally块---This shoud be always run ?"); 22 } 23 } 24 } 25 26 /*InterruptedException 27 当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。 28 有时候,一种方法可能希望测试当前线程是否已被中断,如果已被中断,则立即抛出此异常。*/
原代码执行结果:
start ADaemon...
如果注释语句 // t.setDaemon(true); 后
执行结果:
start ADaemon...
finally块---This shoud be always run ?
二 、如何创建线程
Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法:
◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法;
◆实现Runnalbe接口,重载Runnalbe接口中的run()方法。
为什么Java要提供两种方法来创建线程呢?它们都有哪些区别?相比而言,哪一种方法更好呢?
在Java中,类仅支持单继承,也就是说,当定义一个新的类的时候,它只能扩展一个外部类.这样,如果创建自定义线程类的时候是通过扩展 Thread类的方法来实现的,那么这个自定义类就不能再去扩展其他的类,也就无法实现更加复杂的功能。因此,如果自定义类必须扩展其他的类,那么就可以使用实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性。
还有一点最重要的就是使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享.
(1)通过扩展Thread类来创建多线程
假设一个影院有三个售票口,分别用于向儿童、成人和老人售票。影院为每个窗口放有100张电影票,分别是儿童票、成人票和老人票。三个窗口需要同时卖票,而现在只有一个售票员,这个售票员就相当于一个CPU,三个窗口就相当于三个线程。通过程序来看一看是如何创建这三个线程的。
1 public class test_5 { 2 public static void main(String[] args){ 3 MutiThread t1 = new MutiThread("windows 1"); 4 MutiThread t2 = new MutiThread("windows 2"); 5 MutiThread t3 = new MutiThread("windows 3"); 6 t1.start(); 7 t2.start(); 8 t3.start(); 9 } 10 11 } 12 13 class MutiThread extends Thread 14 { 15 private int ticket = 100;//每个线程都拥有100张票 16 MutiThread(String name){ 17 super(name);//调用父类带参数的构造方法 18 } 19 public void run(){ 20 while(ticket>0){ 21 ticket--; 22 System.out.println(ticket+" is saled by "+this.getName()); 23 } 24 } 25 }
程序中定义一个线程类,它扩展了Thread类。利用扩展的线程类在MutliThreadDemo类的主方法中创建了三个线程对象,并通过start()方法分别将它们启动。
从结果可以看到,每个线程分别对应100张电影票,之间并无任何关系,这就说明每个线程之间是平等的,没有优先级关系,因此都有机会得到CPU的处理。但是结果显示这三个线程并不是依次交替执行,而是在三个线程同时被执行的情况下,有的线程被分配时间片的机会多,票被提前卖完,而有的线程被分配时间片的机会比较少,票迟一些卖完。
可见,利用扩展Thread类创建的多个线程,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。
(2)通过实现Runnable接口来创建多线程
1 public class test_6 { 2 public static void main(String [] args){ 3 MutliThread m1=new MutliThread("Window 1"); 4 MutliThread m2=new MutliThread("Window 2"); 5 MutliThread m3=new MutliThread("Window 3"); 6 Thread t1=new Thread(m1); 7 Thread t2=new Thread(m2); 8 Thread t3=new Thread(m3); 9 t1.start(); 10 t2.start(); 11 t3.start(); 12 } 13 14 } 15 16 class MutliThread implements Runnable{ 17 private int ticket=100;//每个线程都拥有100张票 18 private String name; 19 MutliThread(String name){ 20 this.name=name; 21 } 22 public void run(){ 23 while(ticket>0){ 24 ticket--; 25 System.out.println("ticket "+ticket--+" is saled by "+name); 26 } 27 } 28 }
由于这三个线程也是彼此独立,各自拥有自己的资源,即100张电影票,因此程序输出的结果和(1)结果大同小异。均是各自线程对自己的100张票进行单独的处理,互不影响。
可见,只要现实的情况要求保证新建线程彼此相互独立,各自拥有资源,且互不干扰,采用哪个方式来创建多线程都是可以的。因为这两种方式创建的多线程程序能够实现相同的功能。
由于这三个线程也是彼此独立,各自拥有自己的资源,即100张电影票,因此程序输出的结果和例4.2.1的结果大同小异。均是各自线程对自己的100张票进行单独的处理,互不影响。
可见,只要现实的情况要求保证新建线程彼此相互独立,各自拥有资源,且互不干扰,采用哪个方式来创建多线程都是可以的。因为这两种方式创建的多线程程序能够实现相同的功能。
(3) 通过实现Runnable接口来实现线程间的资源共享
现实中也存在这样的情况,比如模拟一个火车站的售票系统,假如当日从A地发往B地的火车票只有100张,且允许所有窗口卖这100张票,那么每一个窗口也相当于一个线程,但是这时和前面的例子不同之处就在于所有线程处理的资源是同一个资源,即100张车票。如果还用前面的方式来创建线程显然是无法实现的,这种情况该怎样处理呢?看下面这个程序,程序代码如下所示:
1 public class test_7 { 2 public static void main(String [] args){ 3 MutThread m = new MutThread(); 4 Thread t1=new Thread(m,"windows 1"); 5 Thread t2=new Thread(m,"windows 2"); 6 Thread t3=new Thread(m,"windows 3"); 7 t1.start(); 8 t2.start(); 9 t3.start(); 10 } 11 12 } 13 14 class MutThread implements Runnable 15 { 16 private int ticket = 100; 17 public void run() { 18 while(ticket>0){ 19 System.out.println("ticket "+ticket--+" is saled by "+Thread.currentThread().getName()); 20 } 21 } 22 23 }
结果正如前面分析的那样,程序在内存中仅创建了一个资源,而新建的三个线程都是基于访问这同一资源的,并且由于每个线程上所运行的是相同的代码,因此它们执行的功能也是相同的。
可见,如果现实问题中要求必须创建多个线程来执行同一任务,而且这多个线程之间还将共享同一个资源,那么就可以使用实现Runnable接口的方式来创建多线程程序。而这一功能通过扩展Thread类是无法实现的,想想看,为什么?
实现Runnable接口相对于扩展Thread类来说,具有无可比拟的优势。这种方式不仅有利于程序的健壮性,使代码能够被多个线程共享,而且代码和数据资源相对独立,从而特别适合多个具有相同代码的线程去处理同一资源的情况。这样一来,线程、代码和数据资源三者有效分离,很好地体现了面向对象程序设计的思想。因此,几乎所有的多线程程序都是通过实现Runnable接口的方式来完成的。
三 、线程的状态切换
对于线程来讲,从我们创建它一直到线程运行结束,在这个过程中,线程的状态可能是这样的:
- 创建:已经有Thread实例了, 但是CPU还有为其分配资源和时间片。
- 就绪:线程已经获得了运行所需的所有资源,只等CPU进行时间调度。
- 运行:线程位于当前CPU时间片中,正在执行相关逻辑。
- 休眠:一般是调用Thread.sleep后的状态,这时线程依然持有运行所需的各种资源,但是不会被CPU调度。
- 挂起:一般是调用Thread.suspend后的状态,和休眠类似,CPU不会调度该线程,不同的是,这种状态下,线程会释放所有资源。
- 死亡:线程运行结束或者调用了Thread.stop方法。
下面我们来演示如何进行线程状态切换,首先我们会用到下面方法:
- Thread()或者Thread(Runnable):构造线程。
- Thread.start:启动线程。
- Thread.sleep:将线程切换至休眠状态。
- Thread.interrupt:中断线程的执行。
- Thread.join:等待某线程结束。
- Thread.yield:剥夺线程在CPU上的执行时间片,等待下一次调度。
- Object.wait:将Object上所有线程锁定,直到notify方法才继续运行。
- Object.notify:随机唤醒Object上的1个线程。
- Object.notifyAll:唤醒Object上的所有线程。
线程等待与唤醒
这里主要使用Object.wait和Object.notify方法。需要注意的是,wait和notify都必须针对同一个对象,当我们使用实现Runnable接口的方式来创建线程时,应该是在Runnable对象而非Thread对象上使用这两个方法。
线程的休眠与唤醒
1 public class test_8 { 2 3 public static void main(String[] args) throws InterruptedException 4 { 5 sleepTest(); 6 } 7 8 private static void sleepTest() throws InterruptedException 9 { 10 Thread thread = new Thread() 11 { 12 public void run() 13 { 14 System.out.println("线程 " + Thread.currentThread().getName() + "将要休眠5分钟。"); 15 try 16 { 17 Thread.sleep(5*60*1000); 18 } 19 catch(InterruptedException ex) 20 { 21 System.out.println("线程 " + Thread.currentThread().getName() + "休眠被中断。"); 22 } 23 System.out.println("线程 " + Thread.currentThread().getName() + "休眠结束。"); 24 } 25 }; 26 thread.setDaemon(true); 27 thread.start(); 28 Thread.sleep(500); 29 thread.interrupt(); 30 } 31 }
线程在休眠过程中,我们可以使用Thread.interrupt将其唤醒,这时线程会抛出InterruptedException。
结果:
线程 Thread-0将要休眠5分钟。
线程 Thread-0休眠被中断。
线程 Thread-0休眠结束。
线程的终止
虽然有Thread.stop方法,但该方法是不被推荐使用的,我们可以利用上面休眠与唤醒的机制,让线程在处理IterruptedException时,结束线程。
1 public class test_9 { 2 public static void main(String[] args) 3 { 4 stopTest(); 5 } 6 7 private static void stopTest() 8 { 9 Thread thread = new Thread() 10 { 11 public void run() 12 { 13 System.out.println("线程运行。"); 14 try { 15 Thread.sleep(1*60*1000); 16 } catch (InterruptedException e) { 17 System.out.println("线程中断,结束线程"); 18 return ; 19 } 20 System.out.println("线程正常结束"); 21 22 } 23 }; 24 thread.start(); 25 try { 26 Thread.sleep(100); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 thread.interrupt(); 31 } 32 33 }
结果:
线程运行。
线程中断,结束线程
线程的同步等待
当我们在主线程中创建了10个子线程,然后我们期望10个子线程全部结束后,主线程在执行接下来的逻辑,这时,就该Thread.join了。
1 public class test_10 { 2 public static void main(String[] args) throws InterruptedException 3 { 4 joinTest(); 5 } 6 7 private static void joinTest() throws InterruptedException 8 { 9 Thread thread = new Thread() 10 { 11 public void run() 12 { 13 try 14 { 15 for(int i = 0; i < 10; i++) 16 { 17 System.out.println("线程在运行---"+i); 18 Thread.sleep(1000); 19 } 20 } 21 catch(InterruptedException ex) 22 { 23 ex.printStackTrace(); 24 } 25 26 } 27 }; 28 thread.setDaemon(true); 29 thread.start(); 30 thread.join(); 31 System.out.println("主线程正常结束。"); 32 } 33 34 }
结果:
线程在运行---0
线程在运行---1
线程在运行---2
线程在运行---3
线程在运行---4
线程在运行---5
线程在运行---6
线程在运行---7
线程在运行---8
线程在运行---9
主线程正常结束。
注释掉
//thread.join();
结果:
主线程正常结束。
线程在运行---0
参考
Java创建线程的两个方法