异步编程学习之路(四)-睡眠、唤醒、让步、合并

96 阅读4分钟

本文是异步编程学习之路(四)-睡眠、唤醒、让步、合并,若要关注前文,请点击传送门:

异步编程学习之路(三)-多线程之间的协作与通信

前文我们通过wait()、notify()等多线程通信方法实现了线程之间的协同合作,在本文中我们将对这些方法做详细介绍。

一、线程睡眠

让当前正在执行的线程阻塞一段时间,则可以通过调用Thread类的静态sleep()方法来实现。当线程调用sleep()方法后,该线程会进入阻塞状态并暂时让出CPU时间片,而其它任何优先级的线程都可以得到执行的机会,即使系统中没有其它可执行的线程,处于sleep()的线程也不会执行。

代码如下:

/**
 * @Description:线程睡眠、唤醒、让步、合并
 * @Author:zhangzhixaing
 * @CreateDate:2018/12/23 14:23:56
 * @Version:1.0
 */
public class ThreadTest {
    
    public synchronized void business() {
        for (int i = 1; i < 100; i++) {
            if (("thread01".equals(Thread.currentThread().getName())) && (i % 10 ==0)) {                   
                try {
                    System.out.println(String.format("%s困了,要睡觉了。", Thread.currentThread().getName()));
                    Thread.sleep(2000);//将线程睡眠2s
                    System.out.println(String.format("%s睡醒了,继续运行。", Thread.currentThread().getName()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(String.format("%s:当前是第%s次循环", Thread.currentThread().getName(), i));
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest(); 
        new Thread(() -> threadTest.business(), "thread01").start();
        new Thread(() -> threadTest.business(), "thread02").start();
    }
}

运行结果:

thread01:当前是第1次循环
thread01:当前是第2次循环
.
.
thread01:当前是第9次循环
thread01困了,我要关门睡觉了。
thread01醒了,开门。
thread01:当前是第10次循环
.
.
thread01:当前是第99次循环
thread02:当前是第1次循环
.
.
thread01:当前是第99次循环

当线程thread01每次循环判断是否为10的整数倍,如果是则睡眠2s,大家有没有发现,就算thread01进入睡眠状态,thread02也没有执行,而是等到thread01执行完100次之后,thread02才开始执行。因为synchronize加载了方法上所以是对象所,两个线程又同时使用的同一个对象,前文已经介绍过synchronize关键字可以保证线程之间的“可见性”和“有序性”,如果有不太清楚的请回过头看本专栏的前面的synchronize相关文章,传送门:

异步编程学习之路(二)-通过Synchronize实现线程安全的多线程

线程唤醒:调用notify、notifyAll即可。

二、线程让步

yield()方法是一个和sleep()方法有点相似的方法,它可以让当前正在执行的线程暂停,但它不会阻塞该线程,只是将该线程转入就绪状态。当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同并处于就绪状态的线程才会获得执行机会。

代码如下:

/**
 * @Description:线程睡眠、唤醒、让步、合并
 * @Author:zhangzhixaing
 * @CreateDate:2018/12/23 14:23:56
 * @Version:1.0
 */
public class ThreadTest {

    public static void main(String[] args) {
        Yield yield = new Yield();
        Thread thread01 = new Thread(yield, "thread01");
        Thread thread02 = new Thread(yield, "thread02");
        thread01.setPriority(Thread.MIN_PRIORITY);//thread01设置低优先级
        thread02.setPriority(Thread.MAX_PRIORITY);//thread02设置高优先级
        thread01.start();
        thread02.start();
    }    
}

class Yield implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.ou.println(String.format("%s:当前是第%s次循环", Thread.currentThread().getName(), i));
            if(i % 10 == 0) {
                System.out.println(String.format("%s:让步", Thread.currentThread.getName()));
                Thread.yield();
            }
        }
    }
}

运行结果:

thread02:当前是第1次循环
thread01:当前是第1次循环
thread02:当前是第2次循环
.
.
thread02:让步
thread02:当前是第10次循环
thread02:当前是第11次循环
thread02:当前是第12次循环
.
.
thread01:让步
thread01:当前是第10次循环
thread01:当前是第11次循环
thread01:当前是第12次循环

由于thread01和thread02不属于相同优先级的线程,所以不能获得让步以后的CPU时间片。

三、线程中断

线程中断涉及到三个方法,分别是:interrupt 中断线程  、interrupted( 测试当前线程是否已经中断 )、isInterrupted(  测试线程是否已经中断 )。

interrupt()方法用于中断线程,通常的理解来看,只要某个线程启动后,调用了该方法,则该线程不能继续执行了,来看个小例子:

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread("MyThread");
        t.start();
        Thread.sleep(100);// 睡眠100毫秒
        t.interrupt();// 中断t线程
    }
}
class MyThread extends Thread {
    int i = 0;
    public MyThread(String name) {
        super(name);
    }
    public void run() {
        while(true) {// 死循环,等待被中断
            System.out.println(getName() + getId() + "执行了" + ++i + "次");
        }
    }
}

运行后,我们发现,线程t一直在执行,没有被中断,原来interrupt()是骗人的,汗!其实interrupt()方法并不是中断线程的执行,而是为调用该方法的线程对象打上一个标记,设置其中断状态为true,通过isInterrupted()方法可以得到这个线程状态,我们将上面的程序做一个小改动:

public class InterruptTest {
	public static void main(String[] args) throws InterruptedException {
		MyThread t = new MyThread("MyThread");
		t.start();
		Thread.sleep(100);// 睡眠100毫秒
		t.interrupt();// 中断t线程
	}
}
class MyThread extends Thread {
	int i = 0;
	public MyThread(String name) {
		super(name);
	}
	public void run() {
		while(!isInterrupted()) {// 当前线程没有被中断,则执行
			System.out.println(getName() + getId() + "执行了" + ++i + "次");
		}
	}
}

这样的话,线程被顺利的中断执行了。很多人实现一个线程类时,都会再加一个flag标记,以便控制线程停止执行,其实完全没必要,通过线程自身的中断状态,就可以完美实现该功能。如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。 我们可以捕获该异常,并且做一些处理。另外,Thread.interrupted()方法是一个静态方法,它是判断当前线程的中断状态,需要注意的是,线程的中断状态会由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

四、线程合并

 线程合并是优先执行调用该方法的线程,再执行当前线程,来看一个小例子:

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        JoinThread t1 = new JoinThread("t1");
        JoinThread t2 = new JoinThread("t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("主线程开始执行!");
    }
}
class JoinThread extends Thread {
    public JoinThread(String name) {
        super(name);
    }
    public void run() {
        for(int i = 1; i <= 10; i++) {
            System.out.println(getName() + "执行了" + i + "次");
        }
    }
}

运行结果:

t11执行了1次
t11执行了2次
t11执行了3次
.
.
t11执行了10次
t12执行了1次
t12执行了2次
.
.
t12执行了10

五、线程优先级

线程最低优先级为1,普通优先级是5,最高优先级为10,Thread类中提供了优先级的三个常量。

代码如下:

/**
 * @Description:线程睡眠、唤醒、让步、合并
 * @Author:zhangzhixaing
 * @CreateDate:2018/12/23 14:23:56
 * @Version:1.0
 */
public class ThreadTest {

    public static void main(String[] args) {
        Thread thread01 = new Thread(() -> {}, "thread01");
        Thread thread02 = new Thread(() -> {}, "thread02");
        Thread thread03 = new Thread(() -> {}, "thread03");
        thread01.setPriority(Thread.MIN_PRIORITY);//thread01设置低优先级
        thread02.setPriority(Thread.MAX_PRIORITY);//thread02设置普通优先级
        thread03.setPriority(Thread.MAX_PRIORITY);//thread03设置高优先级
    }    
}

通过Thread中的setPriority()方法就可以设置不同线程之间的线程优先级了。

六、线程等待&唤醒

线程等待调用Thread中的wait()方法,线程唤醒调用Thread中的notify()或者notifyAll(),这两个方法的区别在于前者是唤醒当前对象阻塞队列中的任意线程,后者是唤醒当前对象阻塞队列中的全部线程。需要注意的是:wait() 与 notify/notifyAll 方法必须在同步代码块中使用(不论是Lock.lock()还是synchronize)。

代码如下:

/**
 * @Description:多线程之间的协作与通信
 * @Author:zhangzhixiang
 * @CreateDate:2018/12/21 12:53:36
 * @Version:1.0
 */
public class Plate {
 
    private List<Object> foods = new ArrayList<>();
 
    public synchronized void enjoy() {
        while (foods.size() == 0) {
            try {
                wait(10);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        Object food = foods.get(0);
        foods.clear();
        notify();//唤醒阻塞队列线程到就绪队列
        System.out.println(String.format("顾客正在享受%s,好吃点赞。", food));
    }
 
    public synchronized void cooking() {
        while (foods.size() > 0) {
            try {
                wait(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Object food = "牛排";
        foods.add(food);
        notify();//唤醒阻塞队列线程到就绪队列
        System.out.println(String.format("厨师制作%s,并放到顾客的盘子里。", food));
    }
 
    public static void main(String[] args) {
        Plate plate = new Plate();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> plate.cooking()).start();
            new Thread(() -> plate.enjoy()).start();
        }
    }
 
}

本文到此结束,之后的文章就开始学习线程池及其他高阶用法。

异步编程学习之路(五)-线程池