1、wait和sleep的区别
结合synchronized,会更好的理解sleep()和wait()这两个方法,当然也就知道了他们的区别了。这篇博客就一起学习这两个方法
sleep()
sleep() 方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。
因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
下面用一个例子来演示:
Service类:
public class Service {
public void mSleep(){
synchronized(this){
try{
System.out.println(" Sleep 。当前时间:"+System.currentTimeMillis());
Thread.sleep(3*1000);
}
catch(Exception e){
System.out.println(e);
}
}
}
public void mWait(){
synchronized(this){
System.out.println(" Wait 。结束时间:"+System.currentTimeMillis());
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
就定义了两个方法, mSleep()方法会让调用线程休眠3秒,mWait() 就打印一句话。两个方法都使用了同步锁。
SleepThread类:
public class SleepThread implements Runnable{
private Service service;
public SleepThread(Service service){
this.service = service;
}
public void run(){
service.mSleep();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
线程类,用于调用Service 的mSleep方法
WaitThread类:
public class WaitThread implements Runnable{
private Service service;
public WaitThread(Service service){
this.service = service;
}
public void run(){
service.mWait();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
线程类,用于调用Service 的mWait方法
测试类:
public class Test{
public static void main(String[] args){
Service mService = new Service();
Thread sleepThread = new Thread(new SleepThread(mService));
Thread waitThread = new Thread(new WaitThread(mService));
sleepThread.start();
waitThread.start();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
创建了一个Service对象并赋值给mService,还创建了两个线程并传入mService,也就是说两个线程启动后,调用的是同一个Service对象的方法。
先看下结果:
梳理一下逻辑:
首先sleepThread线程会启动起来,然后在run方法里调用Service对象的mSleep方法,到了同步代码块后,this就是Test类里创建的Service对象mService,sleepThread线程获得了Service对象的锁,之后进入了休眠状态,但并没有释放该Service对象的锁。
这时waitThread线程也启动了起来,调用Service对象的mWait方法,同样到了同步代码块,因为Service对象的锁已经被sleepThread占了,所以waitThread线程只能干等着。
等到sleepThread线程执行完毕(休眠结束)后释放了同步锁,waitThread线程拿到了同步锁,会继续执行,mWait才会被调用。
如果sleepThread释放了机锁的话,waitThread 的任务会马上得到执行。从打印结果可以看出,waitThread 的任务是3秒钟之后才得到执行。
同步锁,锁住的是一个对象。如果一个线程拿到了一个对象的机锁去执行一段同步代码块了,那么其他线程都不能执行这个对象的其他同步代码块。
在这个例子中就是sleepThread线程拿到了service对象的同步锁,进入后休眠,但没有释放机锁,那么waitThread线程是不能执行这个service对象的其他同步代码块的,也就就是不能进入这一段代码
synchronized(this){
System.out.println(" Wait 。结束时间:"+System.currentTimeMillis());
}
- 1
- 2
- 3
- 4
相信现在你已经理解了sleep方法没有释放机锁会带来什么结果了,那么继续wait
wait()
wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程
下面修改程序如下所示:
public class Service {
public void mSleep(){
synchronized(this){
try{
Thread.sleep(3*1000);
this.notifyAll();
System.out.println(" 唤醒等待 。 结束时间:"+System.currentTimeMillis());
}
catch(Exception e){
System.out.println(e);
}
}
}
public void mWait(){
synchronized(this){
try{
System.out.println(" 等待开始 。 当前时间:"+System.currentTimeMillis());
this.wait();
}catch(Exception e){
System.out.println(e);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
测试类:
public class Test{
public static void main(String[] args){
Service mService = new Service();
Thread sleepThread = new Thread(new SleepThread(mService));
Thread waitThread = new Thread(new WaitThread(mService));
waitThread.start();
sleepThread.start();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
同样先看下打印结果
这里是先让 waitThread线程启动起来,然后waitThread线程进入等待状态,并释放了Service对象的锁,这时sleepThread也启动了,来到了mSleep方法的同步代码块,因为之前的waitThread线程已经释放了Service对象的机锁,sleepThread可以拿到对象锁,所以mSleep方法是会被马上调用的。然后sleepThread线程就是进入了睡眠状态,等到3秒休眠结束后调用notifyAll()唤醒了waitThread线程。
综上所诉:
sleep() 和 wait() 的区别就是 调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁
2、线程的生命周期(五中状态的切换流程)
Java中的线程的生命周期大体可分为5种状态。
1. 新建(NEW):新创建了一个线程对象。2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
一.线程的状态图
二.初始状态
- 实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态
三.可运行状态
- 可运行状态只是说你资格运行,调度程序没有挑选到你,你就永远是可运行状态。
- 调用线程的start()方法,此线程进入可运行状态。
- 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入可运行状态。
- 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入可运行状态。
- 锁池里的线程拿到对象锁后,进入可运行状态。
四.运行状态
- 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
五.死亡状态
- 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。
- 在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
六.阻塞状态
- 当前线程T调用Thread.sleep()方法,当前线程进入阻塞状态。
- 运行在当前线程里的其它线程t2调用join()方法,当前线程进入阻塞状态。
- 等待用户输入的时候,当前线程进入阻塞状态。
七.等待队列(本是Object里的方法,但影响了线程)
- 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
- 与等待队列相关的步骤和图
- 线程1获取对象A的锁,正在使用对象A。
- 线程1调用对象A的wait()方法。
- 线程1释放对象A的锁,并马上进入等待队列。
- 锁池里面的对象争抢对象A的锁。
- 线程5获得对象A的锁,进入synchronized块,使用对象A。
- 线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入锁池。||||| 线程5调用对象A的notify()方法,唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入锁池。
- notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
- 锁池里面的线程争抢对象锁,但线程1什么时候能抢到就不知道了。||||| 原本锁池+第6步被唤醒的线程一起争抢对象锁。
八.锁池状态
- 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入锁池状态。简言之,锁池里面放的都是想争夺对象锁的线程。
- 当一个线程1被另外一个线程2唤醒时,1线程进入锁池状态,去争夺对象锁。
- 锁池是在同步的环境下才有的概念,一个对象对应一个锁池。
九.几个方法的比较
- Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。作用:给其它线程执行机会的最佳方式。
- Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
- t.join()/t.join(long millis),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
- obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
- obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
十.两个疑问
- 当对象锁被某一线程释放的一瞬间,锁池里面的哪个线程能获得这个锁?随机?队列FIFO?or sth else?
- 等待队列里许许多多的线程都wait()在一个对象上,此时某一线程调用了对象的notify()方法,那唤醒的到底是哪个线程?随机?队列FIFO?or sth else?java文档就简单的写了句:选择是任意性的。
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”,随机从arr数组中获取奖项元素并打印在控制台上,格式如下:
抽奖箱1 又产生了一个 10 元大奖
抽奖箱2 又产生了一个 100 元大奖
//.....
public class ChoujiangDemo {
public static void main(String[] args) {
Chou c = new Chou();
Thread t1 = new Thread(c,"抽奖箱1");
Thread t2 = new Thread(c,"抽奖箱2");
t1.start();
t2.start();
}
}
public class Chou implements Runnable {
int[] arr = { 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300 };
int num = arr.length;
boolean[] flag = new boolean[arr.length];
public void run() {
while (true) {
synchronized (this) {
if (num > 0) {
int index = (int) (Math.random() * arr.length);
int get = arr[index];
// 代表这张抽奖券抽过了
if (flag[index] != true) {
flag[index] = true;
System.out.println(Thread.currentThread().getName()
+ " 又产生了一个" + get + "元大奖");
num--;
}
}
}
}
}
}
4、某公司组织年会,会议入场时有两个入口,在入场时每位员工都能获取一张双色球彩票,假设公司有100个员工,利用多线程模拟年会入场过程,并分别统计每个入口入场的人数,以及每个员工拿到的彩票的号码。线程运行后打印格式如下:
编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是: [17, 24, 29, 30, 31, 32, 07]
编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是: [06, 11, 14, 22, 29, 32, 15]
//.....
从后门入场的员工总共: 13 位员工
从前门入场的员工总共: 87 位员工
package com.lanqiao.demo9;
/**
* 在测试类中主要是实例化子进程 创建进程
* 启动进程 不要调用run'方法 start
*
* @author qichunlin
*
*/
public class Test {
public static void main(String[] args) {
// 实例化子进程
myThread mt = new myThread();
// 创建进程
Thread t1 = new Thread(mt, "前门");
Thread t2 = new Thread(mt, "后门");
// 启动进程
t1.start();
t2.start();
}
}
package com.lanqiao.demo9;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 进场类
* @author qichunlin
*
*/
public class Entering implements Runnable {
// 前门人数
private int frontNum = 0;
// 后门人数
private int backNum = 0;
// 总人数
private int person = 100;
@Override
public void run() {
// 线程体
// 1、统计人数
while (true) {
synchronized (this) {
try {
Thread.sleep(10);
// 获取线程名称
String threadName = Thread.currentThread().getName();
// 1-1:总人数<0,退出循环
if (person < 0) {
return;
}
// 1-2:总人数>0,
if (person > 0) {
// 统计前门人数
if ("前门".equals(threadName)) {
System.out.println("编号为: "+(100-person+1)+" 的员工 从前门 入场!拿到的双色球彩票号码是: "+this.show());
frontNum++;
}
// 统计后门人数
if ("后门".equals(threadName)) {
System.out.println("编号为: "+(100-person+1)+" 的员工 从后门 入场!拿到的双色球彩票号码是: "+this.show());
backNum++;
}
person--;
}
// 1-3:总人数==0,显示前门人数及后门人数
if (person == 0) {
System.out.println("前门人数:"+frontNum);
System.out.println("后门人数:"+backNum);
person--;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 2、显示双色球结果
public List<Integer> show() {
//7 创建集合
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i <7; i++) {
Random r = new Random();
int num = r.nextInt(33)+1;
list.add(num);
}
return list;
}
}