线程控制
java中提供了很多方法,让我们可以对线程进行控制。我们今天主要学习其中的join线程,后台线程,线程睡眠,线程让步和怎么改变线程优先级。
join线程:
join线程可以让一个线程等待另一个线程执行完毕以后再执行。我们还是通过例子来进行学习,有这么一个场景:我们要吃一碗泡面,要经过四个步骤,放水、下面、吃面、洗碗。用我们前面学到的知识来写的话,我们的代码会是这样子:
package com.ljw.Thread;
import com.ljw.StringStringBuilderStringBuffer.Test;
/**
* Created by liujiawei on 2018/7/6.
*/
public class TestJoin implements Runnable{
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestJoin join = new TestJoin();
Thread thread = new Thread(join,"放水");
Thread thread2 = new Thread(join,"下面");
Thread thread3 = new Thread(join,"吃面");
Thread thread4 = new Thread(join,"洗碗");
thread.start();
thread2.start();
thread3.start();
thread4.start();
}
}
我们用四个线程来表示这四个动作,用sleep()方法模拟线程的耗时,我们看下运行结果:
因为线程受系统分配资源的影响,所以每次的结果不一定相同,但是这样肯定是接受不了了的,总不能放完水以后就先去洗碗,吃完了面再去下面吧?这个时候,使用join可以帮助我们实现按照顺序吃面的问题:
测试代码
package com.ljw.Thread;
/**
* Created by liujiawei on 2018/7/6.
*/
public class TestEatNoodle implements Runnable{
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
TestEatNoodle eatNoodle = new TestEatNoodle();
Thread thread = new Thread(eatNoodle,"放水");
Thread thread2 = new Thread(eatNoodle,"下面");
Thread thread3 = new Thread(eatNoodle,"吃面");
Thread thread4 = new Thread(eatNoodle,"洗碗");
thread.join();
thread.start();
thread2.join();
thread2.start();
thread3.join();
thread3.start();
thread4.start();
}
}
还是使用之前的代码,只不过这次我们线程执行前使用join()方法,希望能够按照我们安排的顺序来执行,看下运行结果:
我们再多运行几次看看:
可以看到,现在输出的结果就是想要的顺序了。我们从上面的例子可以看到,我们线程join的顺序,就是线程的执行顺序,也许你还会有疑问,如果之前的线程不执行,是不是后面的都不执行了呢?要是突然有事不吃面了,是不是洗碗这个动作也不会发生了呢?答案是是的,所以join方法有三个重载方法:
join():等待线程执行完成
join(long millis):等待被join的线程最长时间为millis毫秒
join(long millis,int nanos): 等待被join的线程最长时间为millis毫秒+nanos毫微秒
后台线程:
java中有一种线程只在后台运行,为其他线程提供服务,这种线程就是后台线程(Daemon Thread)。也叫守护线程。JVM的gc就是一个典型的后台线程。后台线程的特点就是,当所有的前台线程进入死亡状态以后,后台线程也会随之死亡,因为他就是为其他线程服务的,其他线程都没了,他也就没有存在的意义了。
我们还是用吃面的场景来说明后台线程,只不过这次我们不自己下面了,我们去面馆吃面,但是去的时间比较晚了,店家已经在准备第二天的材料了,我们刚好是最后一位顾客,只要我们吃完,店家就会停止准备材料,准备关门。
测试代码:
package com.ljw.Thread;
/**
* Created by liujiawei on 2018/7/6.
*/
public class TestEatNoodleInMall implements Runnable{
@Override
public void run() {
for (int i = 1; i < 100; i++) {
try {
System.out.println("面馆准备了" + i + "份材料");
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestEatNoodleInMall eatNoodleInMall = new TestEatNoodleInMall();
Thread thread = new Thread(eatNoodleInMall,"");
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 10; i++) {
if(i == 9)
System.out.println("小明吃完了面");
}
}
}
里面的fori循环用来模拟实际场景中的耗时,我们看下运行结果,店家会不会把100份材料全部准备好:
我们可以看到,在小明吃完了面以后,店家就没有再准备材料,它只准备了一份材料,这是因为我们通过setDaemon(true)方法将线程设置为了后台线程,main方法本身是一个前台线程,当main方法执行完毕,也就是小明吃完面以后,店家的准备线程随之死亡。
*前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程;
设置后台线程必须在调用start()方法前设置。
线程睡眠:
线程对象的sleep()方法,一般用来暂停线程,使线程从运行状态转到阻塞状态,直至sleep()时间结束,再重新回到就绪状态。测试代码:
package com.ljw.Thread;
import java.util.Date;
/**
* Created by liujiawei on 2018/7/6.
*/
public class TestSleep implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(new Date());
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestSleep sleep = new TestSleep();
Thread thread = new Thread(sleep);
thread.start();
}
}
运行结果:
可以看到线程每隔1000毫秒输出一次。
和join方法类似,sleep()方法也有两个重载方法,用来指定线程休眠的时间:sleep(long milis):
sleep(long milis,int nanos):
线程让步:
yield()方法和sleep()方法不同,sleep()会让线程回到阻塞状态,而yield()方法会让线程回到就绪状态,直接等到cpu重新分配资源,但只有优先级和该线程相等或大于该线程的其他线程才有机会被执行。我们看下示例:package com.ljw.Thread;
/**
* Created by liujiawei on 2018/7/6.
*/
public class TestYield implements Runnable{
@Override
public void run() {
if(Thread.currentThread().getName().equals("A")){
Thread.currentThread().yield();
}else{
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
TestYield yield = new TestYield();
Thread thread = new Thread(yield,"A");
Thread thread1 = new Thread(yield,"B");
thread.join();
thread.start();
thread1.start();
}
}
我们创建了两个线程A,B,在线程执行体中做了判断,只要是A线程,我们就做线程让步,看下输出结果:
可以看到A并没有被输出到控制台。
线程优先级:
每个线程都有各自优先级,Thread类中设置了三个静态常量来表示优先级:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
main线程的优先级默认是普通优先级,通过setPriority(int)方法,可以改变线程的优先级,需要说明的是,虽然这个方法的参数类型是整型,但是不同操作系统的区别,建议还是使用Thread类的静态常量来表示。优先级更高的线程会被分配更多的cpu资源。
sleep()和yield()的区别:
(1)sleep()方法让线程进入阻塞状态,其他所有处于就绪状态的线程都有机会执行,yield()方法会让线程重新回到就绪状态,但是只有优先级等于或者大于他的线程才会被执行;(2)使用sleep()方法需要捕获异常,yield()不需要;
(3)sleep()方法比yield()方法有更好的移植性,不建议使用yield();