线程状态
线程状态,这个问题好像很容易,但也容易模糊,而且有好几个版本,我就以我自认为对的版本,记录下。
线程6状态
- 新建
当一个线程对象被创建,但还未调用 start 方法时处于新建状态
此时未与操作系统底层线程关联 - 可运行
调用了 start 方法,就会由新建进入可运行
此时与底层线程关联,由操作系统调度执行 - 终结
线程内代码已经执行完毕,由可运行进入终结
此时会取消与底层线程关联 - 阻塞
当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态 - 等待
当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间
当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态 - 有时限等待
当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态
线程疑问
- 什么是上述的等待集合
- 什么是阻塞集合
- notity和notifyall唤醒的线程,有顺序吗
等待集合,阻塞集合
对于同一个对象上锁,只有先拿到这个锁的线程先进入到代码中,其他的线程,再在这个对象的阻塞队列中等待,这就是一种阻塞状态;
针对线程中执行wait方法,就会将线程放入到等待队列中,一旦调用notifyall方法,也就会将所有等待队列中对象,放入到阻塞队列中进行排队等待。
举例代码测试
1、测试第一个是否有对于同一把锁是否有执行顺序
public class TestSynchronized {
static List<Thread> list = new ArrayList<>();
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Thread t = new Thread(() -> {
synchronized (lock) {
System.out.println(String.format("{%s}-thread executed",Thread.currentThread().getName()));
try {
//这里的睡眠没有什么意义,仅仅为了控制台打印的时候有个间隔 视觉效果好
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t" + i);//给每个线程去了一个名字 t1 t2 t3 ....
list.add(t);
}
System.out.println("---启动顺序 调度顺序或者说获取锁的顺序讲道理是正序0--9----");
synchronized (lock) {
for (Thread thread : list) {
//这个打印主要是为了看到线程启动的顺序
System.out.println(String.format("{%s}-启动顺序--正序0-9", thread.getName()));
thread.start();// CPU 调度?
//间隔时间表示启动有先后顺序,保证加入到等待队列中
//虽然我们的启动顺序是正序的(t1--t2),但是调度顺序是错乱的 t2---t1
TimeUnit.MILLISECONDS.sleep(1);
}
System.out.println("-------执行顺序--正序9-0");
}
}
}
结果:放入线程是t0-t9,但是执行时是t9-t0,结合我上面画的图理解
2、测试第二个阻塞队列notifyAll之后的执行顺序
public class TestWaitOrder {
static Object object = new Object();//锁对象
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {//jack
synchronized (object){
System.out.println(String.format("{%s}-没有香烟 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
try {
//jack线程进入阻塞,但是释放了锁
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("{%s}-开始工作,大中华真不错",Thread.currentThread().getName()));
}
}, "jack").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {//jack
synchronized (object){
System.out.println(String.format("{%s}-没有香烟 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
try {
//jack线程进入阻塞,但是释放了锁
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("{%s}-开始工作,大中华真不错",Thread.currentThread().getName()));
}
}, "tom").start();
TimeUnit.SECONDS.sleep(1);
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (object){
//log.debug("没有加钱,先休息不干活");
System.out.println(String.format("{%s}-没有加钱,先休息不干活",Thread.currentThread().getName()));
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("{%s}-干活了----有钱能使鬼推",Thread.currentThread().getName()));
}
}, "rose").start();
//5s之后叫醒jack
TimeUnit.SECONDS.sleep(5);
new Thread(() -> {//jack
synchronized (object){
System.out.println("买了两包大中华,给加钱了");
object.notifyAll();
}
}, "boss").start();
}
}
结果发现:放入顺序是jeck-tom-rose,叫醒之后的顺序是rose-tom-jeck,结合画的图理解
3、notify到底叫醒的是哪个线程,有规律吗?
将上述代码进行改造,只需将notifyAll变成notify,多测试几次,并且把上述线程(jack,tom,rose)顺序进行调整。
结果发现:调用notity之后,叫醒的第一个线程是最先放入等待队列中的(最早执行wait),这和notifyAll矛盾吗?其实不矛盾,可以理解为notify只是将第一个移到阻塞队列中了,nitifyAll是把几个都放到了阻塞队列中了,因为阻塞队列执行是从尾部弹出的,所以就能理解了。
4、等待队列中notify的线程会比阻塞队列的先执行吗?
判断等待队列中唤醒的是否插入到了阻塞队列的尾部,进行代码测试
public class TestWaitAndBlockOrder {
static Object object = new Object();//锁对象
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {//jack
synchronized (object){
System.out.println(String.format("{%s}-没有中华 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("{%s}-开始工作",Thread.currentThread().getName()));
}
}, "jack").start();
TimeUnit.SECONDS.sleep(1);
System.out.println("1s之后主线程获取锁");
System.out.println("因为jack wait释放了锁,主线程能够获取到");
System.out.println("-----------关键看打印顺序----------------");
synchronized (object) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (object) {
System.out.println(String.format("{%S}-那些默默无闻的普通人",Thread.currentThread().getName()));
}
}, "t" + i).start();
TimeUnit.MILLISECONDS.sleep(1);
}
//先唤醒jeck
object.notifyAll();
}
}
}
这不能说明等待的线程加入到阻塞队列的尾部,也可能是巧合,那就调换下object.notifyAll()的位置,把这个代码移到普通线程前面
public class TestWaitAndBlockOrder {
static Object object = new Object();//锁对象
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {//jack
synchronized (object){
System.out.println(String.format("{%s}-没有中华 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("{%s}-开始工作",Thread.currentThread().getName()));
}
}, "jack").start();
TimeUnit.SECONDS.sleep(1);
System.out.println("1s之后主线程获取锁");
System.out.println("因为jack wait释放了锁,主线程能够获取到");
System.out.println("-----------关键看打印顺序----------------");
synchronized (object) {
//先唤醒jeck
object.notifyAll();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (object) {
System.out.println(String.format("{%S}-那些默默无闻的普通人",Thread.currentThread().getName()));
}
}, "t" + i).start();
TimeUnit.MILLISECONDS.sleep(1);
}
//后唤醒杰克
// object.notifyAll();
}
}
}
结果发现:等待队列中的线程唤醒后加入阻塞队列的尾部。
总结
block状态的线程,是有个阻塞的队列中存放的,而且是尾部先执行的意思;
wait状态的线程,有个等待队列中存放的,唤醒之后,加入阻塞队列的尾部;
wait状态先变成block状态,再进行执行,我是这么理解的,先姑且这么理解