在Java实现多线程的程序中,虽然Thread类实现了Runnable接口,但是操作线程的主要方法并不在Runnable接口中,而是在Thread类中。下面列出Thread类中的主要方法。
9.4.1 取得和设置线程名称
在Thread类中可以通过getName()方法取得线程的名称,还可以通过setName()方法设置线程的名称。
线程的名称一般在启动线程前就设置好了,但也允许为已经运行的线程设置名称。 允许两个Thread对象有相同的名称,但应该尽量避免这种情况的方法。
- 如果没有设置名称,系统会为其自动分配名称。 在线程操作中,如果没有为一个线程指定一个名称,则系统在使用时会为线程分配一个名称,名称的格式是: Thread-Xx
例子: 取得和设置线程的名称
class MyThread implements Runnable{
public void run(){
for(int i = 0; i < 3; i++){
System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
}
}
}
public class ThreadNameDemo{
public static void main(String args[]){
MyThread my = new MyThread();
new Thread(my).start();
new Thread(my, "线程-A").start();
new Thread(my, "线程-B").start();
new Thread(my).start();
new Thread(my).start();
}
}
运行结果:
-------------------------------------------------
Thread-0运行, i = 0
Thread-0运行, i = 1
Thread-0运行, i = 2
线程-A运行, i = 0
线程-A运行, i = 1
线程-A运行, i = 2
线程-B运行, i = 0
线程-B运行, i = 1
线程-B运行, i = 2
Thread-1运行, i = 0
Thread-1运行, i = 1
Thread-1运行, i = 2
Thread-2运行, i = 0
Thread-2运行, i = 1
Thread-2运行, i = 2
-------------------------------------------------
从运行结果可以发现,没有设置线程名称的3个线程对象的名称都是很有规律的。分别是Thread-0、Thread-1、Thread-2。从之前讲解到的static关键字可以知道,在Thread类中必然存在一个static类型的属性,用于为线程自动命名。
范例2
class MyThread implements Runnable{ //实现Runnable接口
public void run(){ //覆写接口中的run()方法
for (int i = 0; i < 3; i++){
System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
}
}
}
public class CurrentThreadDemo{
public static void main(String args[]){
MyThread my = new MyThread(); //实例化MyThread对象
new Thread(my,"线程-A").start(); //启动一个新的线程
my.run(); //直接调用Mythread中的run()方法
}
}
运行结果如下:
-------------------------------------------------
main运行, i = 0
main运行, i = 1
main运行, i = 2
线程-A运行, i = 0
线程-A运行, i = 1
线程-A运行, i = 2
-------------------------------------------------
在以上程序中,主方法直接通过Runnable接口的子类对象调用其中的run()方法,另外一个是通过线程对象调用start()方法启动的,从结果可以发现,主方法实际上也是一个线程。
********在java中所有的线程都是同时启动的,哪个线程先抢到CPU资源,哪个就先运行********
- java是多线程编程语言,所以java程序运行时也是以线程的方式运行的,那么主方法也就是一个线程main。
- 每当使用java命令执行一个类时,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程(可以通过运行命令来验证),java本身也具备了垃圾的收集机制。所以在java运行时至少会启动两个线程: 一个是main线程,另外一个就是垃圾收集线程GC!!!
9.4.2 判断线程是否启动
通过Thread类中的start()方法通知CPU这个线程已经准备好启动,然后等待系统给它分配CPU资源,运行此线程。
用户可以通过Thread类中的isAlive()方法来测试线程是否已经启动并且仍然在执行。
范例:判断线程是否启动
class MyThread implements Runnable{ //实现Runnable接口
public void run(){ //覆写接口中的run()方法
for (int i = 0; i < 2; i++){
System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
}
}
}
public class ThreadAliveDemo{
public static void main(String args[]){
MyThread my = new MyThread(); //实例化MyThread对象
Thread t = new Thread(my,"线程-A");
System.out.println("线程开始执行之前 --> " + t.isAlive());
t.start();
System.out.println("线程开始执行之后 --> " + t.isAlive());
for (int i = 0; i < 2; i++){
System.out.println("main线程运行 --> " + i);
}
<span style="font-size:14px;color:#ff0000;">System.out.println("代码执行之后 --> " + t.isAlive());</span>
}
}
上面这段程序的运行结果,在代码执行之后的线程可能仍在执行,也可能已经执行完毕。
-------------------------------------------------
线程开始执行之前 --> false
线程开始执行之后 --> true
main线程运行 --> 0
main线程运行 --> 1
<span style="font-size:14px;color:#ff0000;">代码执行之后 --> true</span>
线程-A运行, i = 0
线程-A运行, i = 1
-------------------------------------------------
-------------------------------------------------
线程开始执行之前 --> false
线程开始执行之后 --> true
...
<span style="font-size:14px;color:#ff0000;">代码执行之后 --> false</span>
-------------------------------------------------
以上代码红色部分输出结果是不确定的,有可能到最后线程已经不存活;但也有可能继续存活。 这主要看哪个线程先执行完!!!
- 因为线程操作的不确定性,所以主线程有可能最先执行完,那么此时其他线程不会受到任何影响,并不会随着主线程的结束而结束!!!
9.4.3 线程的强制运行
在线程操作中,可以使用join()方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
范例: 线程的强制运行
class MyThread implements Runnable{
public void run(){
for (int i = 0; i < 2; i++){
System.out.println(Thread.currentThread().getName() + "运行, --> " + i);
}
}
}
public class ThreadJoinDemo{
public static void main(String args[]){
MyThread mt = new MyThread();
Thread t = new Thread(mt, "线程-A");
t.start();
for (int i = 0; i < 5; i++){
if(i > 1){
try{
<span style="font-size:14px;color:#ff0000;">t.join();</span>
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println("Main线程运行 --> " + i);
}
}
}
运行结果: 主线程main必须等待线程-A完成之后才能继续执行!!
-------------------------------------------------
Main线程运行 --> 0
Main线程运行 --> 1
线程-A运行, --> 0
线程-A运行, --> 1
Main线程运行 --> 2
Main线程运行 --> 3
Main线程运行 --> 4
-------------------------------------------------
-------------------------------------------------
Main线程运行 --> 0
Main线程运行 --> 1
Main线程运行 --> 2
Main线程运行 --> 3
Main线程运行 --> 4
线程-A运行, --> 0
线程-A运行, --> 1
-------------------------------------------------
9.4.4 线程的休眠
可以直接使用Thread.sleep()方法来实现“让一个线程在运行时暂时的休眠”
范例:线程的休眠
class MyThread implements Runnable{
public void run(){
for(int i = 0; i < 3; i++){
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行,i = " + i);
}
}
}
public class ThreadSleepDemo{
public static void main(String args[]){
MyThread mt = new MyThread();
new Thread(mt, "线程-A").start();
}
}
运行结果为:
-------------------------------------------------
线程-A运行,i = 0
线程-A运行,i = 1
线程-A运行,i = 2
-------------------------------------------------
上面程序的每次输出都会间隔5000ms, 达到了延迟操作的效果。
9.4.5 中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。 线程一旦被中断,会产生一个一个异常。。。
范例:线程的中断
class MyThread implements Runnable{
public void run(){
System.out.println("1、 进入run方法了");
try {
Thread.sleep(10000);
System.out.println("2、 已经完成休眠");
}catch(InterruptedException e){
System.out.println("3、 休眠被终止了");
return;
}
System.out.println("4、 run方法正常结束");
}
}
public class ThreadInterruptDemo{
public static void main(String args[]){
MyThread my = new MyThread();
Thread t = new Thread(my, "线程-A");
t.start();
try{
Thread.sleep(2000);
}catch(InterruptedException e){}
t.interrupt();
}
}
运行结果:
-------------------------------------------------
1、 进入run方法了
3、 休眠被终止了
-------------------------------------------------
上面程序,一个线程启动之后进入了休眠状态,原来是要休眠10s之后再继续执行,但是主方法在线程启动之后的两秒就中断了该线程。休眠一旦中断后将执行catch中的代码。
9.4.6 后台线程
在Java程序中,只要前台有一个线程在运行,则整个java进程都不会消失,所以此时可以设置一个后台线程,这样即使java进程结束了,此后台线程依然会继续执行。 要想实现这样的操作,直接使用setDaemon()方法即可。
范例: 后台线程设置
import java.io.*;
class MyThread implements Runnable{
public void run(){
while(true){
System.out.println(Thread.currentThread().getName() + "在运行。"); //输出线程名称
}
}
}
public class ThreadDaemonDemo{
public static void main(String args[]){
MyThread my = new MyThread();
Thread t = new Thread(my, "线程-A");
t.setDaemon(true); //此线程在后台运行
t.start();
try{
System.in.read(); //接收输入,使程序在此停顿,一旦接收到用户输入,main线程终止,自动结束
}catch(IOException e){
}
}
}
运行结果:
若主线程main没有结束(用户没有输入操作),那么后台线程(守护线程)会一直运行,不断输出;如果用户输入后,主线程main会结束,由于上面程序中除了主线程main一个前台线程(用户线程)之外,没有其他的用户线程。所以JVM判断进程当中没有用户线程在运行,会结束掉所有守护线程的运行(包括垃圾收集线程GC),并且结束掉该进程!!!
在线程类MyThread中,尽管run()方法中是死循环的方式,但是程序依然可以执行完,因为方法中的死循环已经设置成后台运行了。。。。
补充:关于用户线程和守护线程
-
所谓守护线程就是运行在程序后台的线程,程序的主线程main(程序已开始启动该线程)不会是守护线程。
-
java虚拟机JVM在运行一个进程时,可能会同时有多个线程在运行,只有当所有的非守护线程都结束的时候,虚拟机的进程才会结束,不管在运行的线程是不是main()线程。
-
main主线程结束了(Non-Daemon thread),如果此时在运行的其他线程是daemon thread,JVM会使得这个线程停止,JVM也停下。如果此时正在运行的其他线程有非守护线程,那么必须等到所有的非守护线程结束了,JVM才会停下来。
-
总值,必须等所有的非守护线程都运行结束了,只剩下守护线程的时候,JVM会停止所有的非守护线程,然后JVM也会自己停止。
-
主线程main是非守护线程。
-
后台线程(守护线程)表示的是当前任务中不是必不可少的线程,例如在main线程中启动了一个后台线程,那么当main线程执行完之后,不管后台线程是否执行完毕都会被杀死。。可以参考垃圾收集机制来理解GC也是一个守护线程!!!
9.4.7 线程的优先级
在java的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时,哪个线程的优先级高,哪个线程的就有可能会先被执行,如下图所示:
在java的线程中使用setPriority()方法可以设置一个线程的优先级,在java的线程中一共有3种优先级,如下表所示:
范例:测试线程优先级
class MyThread implements Runnable{
public void run(){
for (int i = 0; i < 2; i++){
try{
Thread.sleep(20);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行,i = " + i);
}
}
}
public class ThreadPriorityDemo{
public static void main(String args[]){
Thread t1 = new Thread(new MyThread(), "线程-A");
Thread t2 = new Thread(new MyThread(), "线程-B");
Thread t3 = new Thread(new MyThread(), "线程-C");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.NORM_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
运行结果:
-------------------------------------------------
线程-A运行,i = 0
线程-B运行,i = 0
线程-C运行,i = 0
线程-A运行,i = 1
线程-B运行,i = 1
线程-C运行,i = 1
-------------------------------------------------
并不是线程的优先级越高就一定会先执行,哪个线程先执行将由CPU的调度决定。上面的输出结果就说了此情况。
-
主方法的优先级是NORM_PRIORITY。可以通过getPriority()方法来获取线程的优先等级。
9.4.8 线程的礼让
在线程操作中,可以使用yield()方法将一个线程的操作暂时让他其他线程执行。
范例:线程的礼让
class MyThread implements Runnable{
public void run(){
for (int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
if (i == 3){
System.out.println("线程礼让:");
Thread.currentThread().yield(); //线程礼让
}
}
}
}
public class ThreadYieldDemo{
public static void main(String args[]){
MyThread my = new MyThread();
Thread t1 = new Thread(my, "线程-A");
Thread t2 = new Thread(my, "线程-B");
t1.start();
t2.start();
}
}
运行结果:
-------------------------------------------------
线程-A运行, i = 0
线程-A运行, i = 1
线程-A运行, i = 2
线程-A运行, i = 3
线程礼让:
线程-B运行, i = 0
线程-B运行, i = 1
线程-B运行, i = 2
线程-B运行, i = 3
线程礼让:
线程-A运行, i = 4
线程-B运行, i = 4
-------------------------------------------------
每当线程满足条件(I == 3),就会将本线程暂停,而让其他线程执行。
9.5 线程操作范例
设计一个线程操作类,可以产生3个线程对象,并分别设置三个线程的休眠时间,具体如下所示:
- 线程A,休眠10秒
- 线程B,休眠20秒
- 线程C,休眠30秒
9.5.1 实现一 继承Thread类
class MyThread extends Thread{
private int time;
public MyThread(String name, int time){
super(name);
this.time = time;
}
public void run(){
try{
Thread.sleep(this.time);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程, 休眠" + this.time + "毫秒");
}
}
public class ExecDemo01{
public static void main(String args[]){
MyThread my1 = new MyThread("线程-A", 10000);
MyThread my2 = new MyThread("线程-B", 10000);
MyThread my3 = new MyThread("线程-C", 10000);
my1.start();
my2.start();
my3.start();
}
}
运行结果:
-------------------------------------------------
线程-A线程, 休眠10000毫秒
线程-B线程, 休眠10000毫秒
线程-C线程, 休眠10000毫秒
-------------------------------------------------
9.5.2 实现二 实现Runnable接口
class MyThread implements Runnable{
//private String name;
private int time;
/*public MyThread(String name, int time){
this.name = name;
this.time = time;
}*/
public MyThread(int time){
this.time = time;
}
public void run(){
try{
Thread.sleep(this.time);
}catch(InterruptedException e){
e.printStackTrace();
}
//System.out.println(this.name + "线程,休眠" + this.time + "毫秒"); //此时根本就没有调用线程的真正线程名。
System.out.println(Thread.currentThread().getName() + "线程,休眠" + this.time +"毫秒");
}
}
public class ExecDemo02{
public static void main(String args[]){
MyThread my1 = new MyThread(10000);
MyThread my2 = new MyThread(20000);
MyThread my3 = new MyThread(30000);
Thread t1 = new Thread(my1, "线程-A");
Thread t2 = new Thread(my2, "线程-B");
Thread t3 = new Thread(my3, "线程-C");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
-------------------------------------------------
线程-A线程,休眠10000毫秒
线程-B线程,休眠20000毫秒
线程-C线程,休眠30000毫秒
------------------------------------------------