在多线程学习的过程中涉及的方法和接口特别多,本文就详细讲解下经常使用方法的作用和使用场景。
1.sleep()方法。
当线程对象调用sleep(time)方法后,当前线程会等待指定的时间(time),并让出cpu执行权,但是它的监控状态依然当前对象的保持者(不会释放对象锁),当指定的时间到了又会自动恢复运行状态。
2.wait()和notify()/notifyAll()方法。
wait()和notify()、notifyAll()方法的调用都必须在synchronized修饰的方法或者代码块中调用,使用过程中必须获得对象锁,否则会抛出java.lang.IllegalMonitorStateException的异常。
执行wait()方法,会使当前线程的状态变为阻塞状态并交出对象锁。
执行notify()方法,会随机挑选一个当前对象阻塞队列中的线程并将状态变为就绪状态。
执行notifyAll()方法,会将当前对象阻塞队列中的所有线程的状态变为就绪状态。
wait()和notify()、notifyAll()为什么都是Object类中的方法。因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
3.join()方法。
在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。并且join()方法是会释放锁的,因为底层使用 wait() 方法来实现的。
4.yield()方法。
yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。(不会释放锁)
因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
4.interrupt()和isInterrupted()/interrupted()方法。
thread.interrupt(),当thread对象变为中断状态,interrupt()并不能中断在运行中的线程,它只能改变中断状态而已。
thread.interrupted(),判断当前线程对象的状态是否为中断状态,内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态。
thread.isInterrupted(),判断当前线程对象的状态是否为中断状态,不会重置当前线程的中断状态。
5.CountDownLatch类。
CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
1 CountDownLatch countDownLatch=new CountDownLatch(5);//CountDownLatch初始化等待5个线程,等待5个线程执行完主线程再执行。
2
3 countDownLatch.await();//主线程阻塞
4
5 countDownLatch.await(1000, TimeUnit.SECONDS);//主线程阻塞,超时后count还没有为0的话,主线程执行。
6
7 countDownLatch.countDown();//将count值减1,当count值为0后,主线程执行。
使用场景:
①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的
CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1) ,多个线程在开始执行任务前首先
coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
③死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
6.LockSupport类。
LockSupport可以起到和wait()一样的作用。在没有LockSupport之前,线程的挂起和唤醒咱们都是通过Object的wait和notify/notifyAll方法实现。
public classTestObjWait {public static void main(String[] args)throwsException {
Thread A= new Thread(newRunnable() {
@Overridepublic voidrun() {int sum = 0;for(int i=0;i<10;i++){
sum+=i;
}
LockSupport.park();//阻塞线程
System.out.println(sum);
}
});
A.start();//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
Thread.sleep(1000);
LockSupport.unpark(A);//唤醒阻塞线程
}
}
总结一下,LockSupport比Object的wait/notify有两大优势:
①LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。
②unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。