控制线程
join线程
Thread提供了一个线程等待另一个线程完成的方法:join方法。当在某个程序执行流中调用其他线程的join方法,调用join方法的那个线程将被阻塞,直到被join方法加入的join线程完成为止。
join方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题配一个线程,当所有的小问题得到处理后,再调用主线程来进一步操作。
例:
class JoinThread implements Runnable{
@Override
//重写run()方法,定义线程执行体
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class FirstThread {
public static void main(String[] args) throws InterruptedException{
//启动子线程
JoinThread joinThread = new JoinThread();
new Thread(joinThread,"新线程").start();
for(int i = 0;i < 100;i++){
if(i == 20){
Thread thread = new Thread(joinThread,"被Join的线程");
thread.start();
//main线程调用了thread线程的join方法
//main线程必须等thread线程执行结束才会向下执行
thread.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
上面程序一共有3条线程:
主线程开始之后启动了名为“新线程”的线程,该子线程将会和main线程并发执行
当主线程的循环变量i等于20时,启动了名为“被join的线程”的线程,然后这个线程join进了main线程。注意:此时“被join的线程”不会和main线程并发执行,而是main线程必须等该线程执行结束后才可以向下执行。在“被join的线程”执行时,实际上只有两条子线程(“新线程” 和 “被join的线程”)并发执行,而main线程处于等待(阻塞)状态直到“被join的线程”执行完。
输出结果:
新线程 0
main 0
新线程 1
main 1
新线程 2
main 2
新线程 3
...
新线程 19
main 19
新线程 20
...
被Join的线程 0
新线程 26
被Join的线程 1
新线程 27
被Join的线程 2
...
被Join的线程 98
被Join的线程 99
main 20
main 21
main 22
...
main 98
main 99
join方法有三种重载的形式:
join():等待被joind额线程执行完成
join(long millis):等待被Join的线程的时间最长为millis毫秒。如果在millis毫秒内,被join的线程还没有执行结束则不再等待
join(long millis,int nanos):等待被join的线程的时间最长为millis毫秒加上nanos微秒
后台进程(Daemon Thread)
指 在后台运行的 线程,任务是为其他的线程提供服务。 JVM的垃圾回收线程就是典型的后台线程。
特征:如果所有的前台线程都死亡,那么后台线程会自动死亡。==当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。
设置指定线程为后台线程: 调用Thread对象的setDaemon()方法
前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程
例:
class DeamonThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class FirstThread {
public static void main(String[] args) throws InterruptedException{
DeamonThread deamonThread = new DeamonThread();
Thread thread = new Thread(deamonThread,"后台线程");
thread.setDaemon(true);
thread.start();
for(int i = 0;i < 10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
上面代码在main方法里先将thread设置为后台线程,然后启动该线程(==要将某个线程设置为后台线程,必须在该线程启动之前设置,setDaemon(true)必须在start()之前调用,否则会引发IllegalThreadStateException==)。本来该线程应该执行到i = 99才会结束,但是实际上它无法运行到99,因为主线程(程序中唯一的前台线程)运行结束后,JVM会自动退出,后台线程也就自动死亡了。
输出结果:
后台线程 0
main 0
后台线程 1
main 1
后台线程 2
...
main 7
后台线程 8
main 8
后台线程 9
main 9
后台线程 10
后台线程 11
后台线程 12
后台线程 13
后台线程 14
后台线程 15
后台线程 16
后台线程 17
后台线程 18
后台线程 19
后台线程 20
后台线程 21
后台线程 22
线程睡眠(sleep)
sleep方法:Thread类的静态方法,让当前正在执行的线程暂停一段时间,并且进入阻塞状态。当当前线程调用sleep方法进入阻塞状态之后,在它sleep的时间里,它不会获得执行的机会。就算系统中没有其他可运行的程序,处于sleep的线程也不会运行,因此sleep方法常用于暂停程序的运行。
sleep有两种重载方法:
- 让当前正在执行的线程暂停millis个毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器精度和准确度的影响。
static void sleep(long millis)
- 让当前正在执行的线程暂停millis个毫秒加nanos微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器精度和准确度的影响,但很少使用。
static void sleep(long millis, int nanos)
例:
class SleepThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 50){
try {
Thread.sleep(2000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
}
public class FirstThread {
public static void main(String[] args) throws InterruptedException{
SleepThread sleepThread = new SleepThread();
Thread thread = new Thread(sleepThread,"新线程");
thread.start();
}
}
上面程序中将当前执行的线程暂停2秒,运行上面程序将看到当该线程执行到i=50的时候,该线程将会暂停两秒然后再继续执行。
线程让步(yield)
和sleep有点类似,也是Thread类的一个静态方法。它也可以让当前正在执行的线程暂停,但不会使线程阻塞,只是将线程转入就绪状态。
yield只是让当前线程暂停一下下,让系统的线程调度器重新调度一次。完全可能出现的情况是:当某个线程调用了yield方法暂停之后,线程调度器又将它调度出来执行。
调用了yield之后,只有优先级与当前线程相同或者比当前线程高的就绪状态的线程才会获得执行的机会。也就是说,如果当前线程优先级设成了Thread.MAX_PRIORITY的话,它yield之后其他线程不会获得执行机会(除非其他线程中也有MAX——PRIORITY的线程),线程调度器又把它调出来运行。
例:
public class YieldTest extends Thread {
public YieldTest(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i == 30) {
this.yield();
}
}
}
public static void main(String[] args) {
YieldTest yt1 = new YieldTest("张三");
//ty1.setPriority(Thread.MAX_PRIORITY);
yt1.start();
YieldTest yt2 = new YieldTest("李四");
//ty2.setPriority(Thread.MIN_PRIORITY);
yt2.start();
}
}
上面程序中调用yield静态方法让当前正在执行的线程暂停,让系统线程调度器重新调度。由于我们把设置优先级的两行代码注释掉,因此两条线程的优先级完全一样,所以当一条线程使用yield静态方法暂停之后,另一条线程就会开始运行。反之,我们取消掉两行注释,也就是为两条线程设置不一样的优先级,则高优先级的线程调用yield方法暂停之后,系统中没有与之优先级相同或优先级更高的线程,因此该线程继续执行。
注意:
优先级更高的线程并不是一定能最快得到执行,只是执行的概率较大。
sleep和yield的区别
改变线程优先级
每个线程执行时都有一定的优先级,优先级高的线程获得较多的执行机会,优先级低的获得较少的执行机会。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,默认情况下,main线程的具有普通的优先级,它创建的子线程也具有普通的优先级
通过setPriority(int newPriority)来设置指定线程的优先级,参数可以是一个1 ~ 10之间的整数,也可以使用Thread类的三个静态常量:
MAX_PRIORITY : 10
MIN_PRIORITY : 1
NORM_PRIORITY : 5
虽然java提供了10个优先级别,但是这些优先级需要操作系统的支持。不同的操作系统上优先级并不相同,而且也不能很好的和java中的10个优先级对应。——> 所以应该尽量避免直接为线程指定优先级,而是使用三个经常常量来设置优先级,这样才可以保证程序具有最好的可移植性。
- 通过getPriority()来返回当前线程的优先级