1.概述
使用Thread相对来说比较简单,没有什么成本,但是通常来说,我们使用线程基本就是覆写run方法,然后调用线程对象的start函数启动线程。
对于面试人员来说,这些都不是面试官会问到的问题,而线程的wait、sleep、join、yied这几个函数可问的就比较多了。
函数名 | 作用 |
wait |
当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁,使得其他线程可以访问。用户可以使用notify、notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。 注意:wait()、notify()、notifyAll()必须放在synchronized block中,否则会抛出异常 |
sleep | 该函数是Thread的静态函数,作用是使调用线程进入睡眠状态。因为sleep()是Thread类的Static方法,因此他不能改变对象的机锁。所以,当在一个Sychronized块中调用sleep()方法,线程虽然休眠了,但是对象的机锁并没有释放,其他线程无法访问这个对象(即使睡着也持有对象锁) |
join | 等待目标线程执行完成之后再继续执行 |
yield | 线程礼让。目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行是未知数 |
2.实际运用
(1)wait、notify
public class ThreadFunTest { private static Object sLockObject = new Object(); static void waitAndNotifyAll() { System.out.println("主线程运行"); Thread thread = new WaitThread(); thread.start(); long startTime = System.currentTimeMillis(); try { synchronized (sLockObject) { System.out.println("主线程等待"); sLockObject.wait(); } } catch (Exception ignored) { } long timsMs = (System.currentTimeMillis() - startTime); System.out.println("主线程继续-->等待耗时:" + timsMs + "ms"); } static class WaitThread extends Thread { @Override public void run() { try { synchronized (sLockObject) { Thread.sleep(3000); sLockObject.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThreadFunTest.waitAndNotifyAll(); } }
执行结果:
在waitAndNotifyAll()函数中,会启动一个WaitThread线程,在该线程中将会调用sleep函数睡眠3秒,线程启动后在主线程调用sLockObject的wait函数,使主线程进入等待状态,此时将不会继续执行。等WaitThread在run函数睡眠3秒后会调用sLockObject的notifyAll函数,此时就会重新唤醒正在等待中的主线程,因此会继续执行下去。
wait、notify机制通常用于等待机制的实现,当条件为满足时调用wait进入等待状态,一旦条件满足,调用notify或notifyAll唤醒等待的线程继续执行。
(2)join
与wait、sleep的浅显易懂不同的是join的作用并不那么直观,也许就是这个原因,使得join函数并不那么容易理解。join函数的原始解释为”Blocks the current Thread(Thread.currentThread())until the receiver finishes its execution and dies“,意思就是阻塞当前调用join函数时所在的线程,直到接收线程执行完毕后再继续。
static void joinDemo() { Worker worker1 = new Worker("work-1"); Worker worker2 = new Worker("work-2"); worker1.start(); System.out.println("启动线程1"); try{ //调用work1的join函数,主线程会阻塞直到work1执行完成 worker1.join(); System.out.println("启动线程2"); //再启动线程2,并且调用线程2的join函数,主线程会阻塞直到worker2执行完成 worker2.start(); worker2.join(); }catch (Exception ignored){ } System.out.println("主线程继续执行"); } static class Worker extends Thread { public Worker(String name) { super(name); } @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("work in " + getName()); } } public static void main(String[] args) { ThreadFunTest.joinDemo(); }
执行结果:
这是因为再joinDemo函数中,首先创建了两个子线程,然后启动了worker1,下一步再调用worker1的join函数。此时主线程进入阻塞状态,一直到worker1执行完毕后才开始继续执行。因为Worker的run函数会睡眠2秒,因此,在主线程中每次调用join实际上都会阻塞2秒,直到run执行完毕再继续。所以,上述逻辑为启动线程1--等待线程1执行完成--启动线程2--等待线程2执行完成--继续执行主干代码。
(3)yied
yied函数与join类似,官方解释为”Causes the calling Thread to yield execution time to another Thread that is read to run“。意思是使调用该函数的线程让出执行时间给其他已经就绪状态的线程。我们知道,线程的执行是有时间片的,每个线程轮流占用CPU固定的时间,执行周期到了之后就让出执行权给其他线程。而yield的功能就是主动让出线程的执行权给其他线程,其他线程能否得到优先执行就得看各个线程的状态了。示例:
static void yieldDemo() { YieldThread t1 = new YieldThread("thread-1"); YieldThread t2 = new YieldThread("thread-2"); t1.start(); t2.start(); } static class YieldThread extends Thread { public YieldThread(String name) { super(name); } public synchronized void run() { for (int i = 0; i < 5; i++) { System.out.println(String.format("%s [%d] ---> %d", this.getName(), this.getPriority(), i)); //当i为2时调用当前线程的yield函数 if (i == 2) { Thread.yield(); } } } } public static void main(String[] args) { ThreadFunTest.yieldDemo(); }
执行结果:
通常情况下t1首先执行,让t1的run函数执行到i等于2时让出当前线程的执行时间,因此,我们看到执行到的thread-2 等于2之前交替执行,等于2时,thread-2让出执行时间,执行thread-1,thread-1执行到2时又将执行权让出,执行thread-2直到完毕再开始执行thread-1.
调用yield就是让出当前线程的执行权,这样一来就王其他线程得到优先执行。