join线程
Thread提供了让一个线程等待另一个线程完成的方法join(),当在某个程序执行流中调用其它线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止
join的使用场景一般是由使用线程的程序调用,从而将大问题划分成许多小问题,每个小问题分配一个线程,当所有的小问题都得到处理后,再调用主线程来进一步操作
public class JoinThread extends Thread
{
// 提供一个有参数的构造器,用于设置该线程的名字
public JoinThread(String name)
{
super(name);
}
// 重写run()方法,定义线程执行体
public void run()
{
for (var i = 0; i < 100; i++)
{
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws Exception
{
// 启动子线程
new JoinThread("新线程").start();
for (var i = 0; i < 100; i++)
{
if (i == 20)
{
var jt = new JoinThread("被Join的线程");
jt.start();
// main线程调用了jt线程的join()方法,main线程
// 必须等jt执行结束才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
主方法开始时就启动了名为“新线程”的子线程,该子线程将会和main线程并发执行,当主线程的循环变量i等于20时,启动了名为“被Join的线程”的线程,该线程不会和main线程并发执行,main线程必须等该线程执行结束后才可以向下执行,在名为“被Join的线程"的线程执行时,实际上只有两个子线程并发执行,而主线程处于等待状态。
实际上主线程执行到i=20时,程序启动并join了名为“被Join的线程"的线程,所以主线程将一直处于阻塞状态,知道名为"被Join的线程"的线程执行完成
join()方法有3种重载形式:
- join():等待被join的线程执行完成
- join(long millis):等待被join的线程时间最长为millis毫秒,如果在该时间内被join的线程还没有结束,则不再等待
- join(long millis, int nanos):等待被join的线程时间最长为millis毫秒加nanos毫微秒
后台线程
有一种线程是运行在后台的,其任务是为其他线程提供服务,这种线程被称为后台线程(Daemon Thread),有成为"守护线程"或者"精灵线程",JVM的垃圾回收线程就是典型的后台线程,它有个特征,如果所有的前台线程都死亡,后台线程会自动死亡
调用Thread对象的setDaemon(true)方法将指定线程设置成后台线程:
public class DaemonThread extends Thread
{
// 定义后台线程的线程执行体与普通线程没有任何区别
public void run()
{
for (var i = 0; i < 1000; i++)
{
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args)
{
var t = new DaemonThread();
// 将此线程设置成后台线程
t.setDaemon(true);
// 启动后台线程
t.start();
for (var i = 0; i < 10; i++)
{
System.out.println(Thread.currentThread().getName() + " " + i);
}
// -----程序执行到此处,前台线程(main线程)结束------
// 后台线程也应该随之结束
}
}
执行代码可以看到的是,t是后台线程,启动该线程应该能执行到i=999,
但主线程是唯一的前台线程且早早就执行完毕,JVM就主动退出了,因此后台线程无法执行完也就是i到不了999,主线程默认就是前台线程,其实t线程默认也是前台线程,有些线程默认就是后台线程【前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程】
- Thread类还提供了一个isDaemon()方法,用于判断指定线程是否为后台线程
- 前台线程死亡后,JVM会通知后台线程死亡
- 将某个线程设置为后台线程必须在该线程启动之前设置,否则会引发IllegalThreadStateException异常
线程睡眠【sleep】
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现,它有两种重载形式:
- static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法收到系统计时器和线程调度器的的精度与准确度的影响
- static void sleep(long millis, int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态,该方放受到系统计时器和线程调度器的精度和准确度的影响
当当前线程调用sleep()方法进入阻塞状态后,在sleep这段时间内,该线程不会获得执行的机会,即便系统中没有其他可执行的线程
import java.util.*;
public class SleepTest
{
public static void main(String[] args)
throws Exception
{
for (var i = 0; i < 10; i++)
{
System.out.println("当前时间: " + new Date());
// 调用sleep方法让当前线程暂停1s。
Thread.sleep(1000);
}
}
}
还有个类似的方法yield()静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,只是将该线程转入就绪状态,让当前线程暂停一下,系统的线程调度器重新调度一次,当然还可能再调度这个线程执行
实际上当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行机会
sleep和yield的区别
- sleep()方法暂停当前线程后,会给其他线程执行机会,不会关心其他线程的优先级,但yield()方法只会给优先级相同的或者优先级更高的线程执行机会
- sleep()方法会将线程转入阻塞状态,知道阻塞时间过了才会转入就绪状态,yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态
- sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显示声明抛出该异常,而yield()方法则没有声明抛出任何异常
- sleep()方法比yield()方法有更好的可移植性,通常提倡使用yield()方法来控制并发线程执行
线程优先级
每个线程执行都有优先级,优先级搞得线程获得较多的执行机会,反之则获得较少的执行机会,默认情况下,线程的优先级与创建它的父线程的优先级相同,比如默认情况下main线程是普通优先级,由main线程创建的子线程也有普通优先级
Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority()方法的参数可以是一个1到10的整数,也可以使用Thread类的三个静态常量
- MAX_PRIORITY,值是10
- MIN_PRIORITY,值是1
- NORM_PRIORITY,值是5
public class PriorityTest extends Thread
{
// 定义一个有参数的构造器,用于创建线程时指定name
public PriorityTest(String name)
{
super(name);
}
public void run()
{
for (var i = 0; i < 50; i++)
{
System.out.println(getName() + ",其优先级是:" + getPriority() + ", 循环变量的值为:" + i);
}
}
public static void main(String[] args)
{
// 改变主线程的优先级
Thread.currentThread().setPriority(6);
for (var i = 0; i < 30; i++)
{
if (i == 10)
{
var low = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:" + low.getPriority());
// 设置该线程为最低优先级
low.setPriority(Thread.MIN_PRIORITY);
}
if (i == 20)
{
var high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级:" + high.getPriority());
// 设置该线程为最高优先级
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}
首先改变了主线程的优先级,设置为6,由main线程创建的子线程的优先级也就是6,所以程序直接输出low、high两个线程的优先级时应该看到6
虽然Java提供了10个等级,但各操作系统支持的等级划分不太一致,因此应该尽量避免直接指定优先级,尽可能使用MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY三个静态常量,这样能保证更好的移植性