线程的生命周期
Thread核心方法
方法 | 功能 | 锁 |
---|---|---|
sleep() | 线程阻塞状态 | 不会释放锁 |
suspend() | 使线程进入阻塞状态 | 不会释放锁 |
resume() | 唤醒suspend | |
wait() | 使线程进入阻塞状态 | 会释放锁 |
notify() | 唤醒wait() | |
yield() | 让优先级高的线程插个队先执行 | 不会释放锁 |
1. sleep() 方法:
sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态。(暂停线程,不会释放锁,睡眠结束,线程继续执行,线程自动释放锁)
public class ThreadTest {
public static void test() {
new Thread (() -> {
synchronized (ThreadTest.class) {
System.out.println ("线程一获得了ThreadTest对象锁");
try {
Thread.sleep (5000L);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println ("执行结束自动释放锁");
}
}).start ();
new Thread (() -> {
try {
Thread.sleep (1000L);
} catch (InterruptedException e) {
e.printStackTrace ();
}
synchronized (ThreadTest.class) {
System.out.println ("线程二获得了ThreadTest对象锁");
}
}).start ();
}
public static void main(String[] args) {
test ();
}
}
控制台输出:
线程一获得了ThreadTest对象锁
执行结束自动释放锁
线程二获得了ThreadTest对象锁
分析:由于线程一率先获得了对象锁,开始执行sleep方法,此时线程一处于阻塞状态,而sleep并不会释放锁。导致线程二未能拿到锁,线程二处于阻塞状态。等待线程一睡眠时间完毕,线程一继续运行,线程一执行完毕自动释放锁,线程二成功获得对象锁,开始执行线程二同步代码块。
2.suspend() 和 resume() 方法:
挂起和唤醒线程,suspend()使线程进入阻塞状态,只有对应的resume()被调用的时候,线程才会进入可执行状态。(suspend不会释放锁,等待resume唤醒,线程继续执行,执行完毕,自动释放锁,不建议用,容易发生死锁,该方法已被JDK弃用)
import java.util.concurrent.TimeUnit;
public class ThreadTest {
public static void test() {
Thread t1 = new Thread (() -> {
synchronized (ThreadTest.class) {
System.out.println ("线程一获得了ThreadTest对象锁");
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println ("执行结束自动释放锁");
}
});
Thread t2 = new Thread (() -> {
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
synchronized (ThreadTest.class) {
System.out.println ("线程二获得了ThreadTest对象锁");
}
});
t1.start ();
t2.start ();
try {
System.out.println ("主线程休眠1秒钟");
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
t1.suspend ();
System.out.println ("线程一被挂起");
}
public static void main(String[] args) {
test ();
}
}
控制台输出:
主线程休眠1秒钟
线程一获得了ThreadTest对象锁
线程一被挂起
分析:由于线程一率先获得了对象锁,而suspend并不会释放锁,如果没被resume唤醒,程序出现死锁,线程二无法获得对象锁,永远不会被执行。
resume()
import java.util.concurrent.TimeUnit;
public class ThreadTest {
public static void test() {
Thread t1 = new Thread (() -> {
synchronized (ThreadTest.class) {
System.out.println ("线程一获得了ThreadTest对象锁");
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println ("线程一被唤醒");
System.out.println ("执行结束自动释放锁");
}
});
Thread t2 = new Thread (() -> {
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
synchronized (ThreadTest.class) {
System.out.println ("线程二获得了ThreadTest对象锁");
}
});
t1.start ();
t2.start ();
try {
System.out.println ("主线程休眠1秒钟");
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
t1.suspend ();
System.out.println ("线程一被挂起");
t1.resume ();
}
public static void main(String[] args) {
test ();
}
}
控制台输出:
主线程休眠1秒钟
线程一获得了ThreadTest对象锁
线程一被挂起
线程一被唤醒
执行结束自动释放锁
线程二获得了ThreadTest对象锁
分析:由于线程一率先获得了对象锁,接着线程一被挂起,线程一阻塞,并没有释放锁,等待被唤醒,线程二没有拿到锁,处于阻塞状态,直到主线程执行resume,线程一被成功唤醒,线程一执行结束释放锁,线程二拿到锁,执行同步代码块。
3.wait() 和 notify() 方法:
两个方法搭配使用,wait()使线程进入阻塞状态,调用notify()时,线程进入可执行状态。wait()内可加或不加参数,加参数时是以毫秒为单位,当到了指定时间或调用notify()方法时,进入可执行状态。(属于Object类,而不属于Thread类,wait()会先释放锁,再执行等待的动作。由于wait()所等待的对象必须先锁住,因此,它只能用在同步化程序段或者同步化方法内,否则,会抛出异常IllegalMonitorStateException.)
wait()
import java.util.concurrent.TimeUnit;
public class ThreadTest {
private static ThreadTest lock = new ThreadTest ();
public static void test() {
new Thread (() -> {
synchronized (lock) {
System.out.println ("线程一获得了ThreadTest对象锁");
try {
lock.wait ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println ("执行结束自动释放锁");
}
}).start ();
new Thread (() -> {
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
synchronized (lock) {
System.out.println ("线程二获得了ThreadTest对象锁");
}
}).start ();
}
public static void main(String[] args) {
test ();
}
}
控制台输出:
线程一获得了ThreadTest对象锁
线程二获得了ThreadTest对象锁
分析:由于线程一率先获得了对象锁,执行同步代码块,wait方法被执行,线程一处于等待状态,由于wait方法会先释放对象锁,线程二成功获得锁,执行同步代码块。
两个线程交换这输出
public class DemoApplication {
public static Object o = new Object();
public static int i = 0;
public static void main(String[] args) {
new Thread(() -> {
synchronized (o) {
while (i < 10) {
System.out.println("Thread1-" + ++i);
o.notify();
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}).start();
new Thread(() -> {
synchronized (o) {
while (i < 10) {
System.out.println("Thread2-" + ++i);
o.notify();
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
notify()
import java.util.concurrent.TimeUnit;
public class ThreadTest {
private static ThreadTest lock = new ThreadTest ();
public static void test() {
new Thread (() -> {
synchronized (lock) {
System.out.println ("线程一获得了ThreadTest对象锁");
try {
lock.wait ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println ("线程一被唤醒成功");
}
}).start ();
new Thread (() -> {
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
synchronized (lock) {
System.out.println ("线程二获得了ThreadTest对象锁");
lock.notify ();
}
}).start ();
}
public static void main(String[] args) {
test ();
}
}
控制台输出:
线程一获得了ThreadTest对象锁
线程二获得了ThreadTest对象锁
线程一被唤醒成功
分析:由于线程一率先获得了对象锁,接着执行wait方法,处于等待状态,wait方法会自动释放锁,线程二拿到锁,执行notify方法,线程一被唤醒成功,线程一继续运行。
4. yield() 方法:
该方法与sleep()类似,只是不能由用户指定暂停多长时间,但此时线程任然处于可执行状态,随时可以再次分得cpu时间片。yield()方法只能使同优先级的线程有执行的机会。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知,不会释放锁)
import java.util.concurrent.TimeUnit;
public class ThreadTest {
private static ThreadTest lock = new ThreadTest ();
public static void test() {
new Thread (() -> {
synchronized (lock) {
System.out.println ("线程一获得了ThreadTest对象锁");
Thread.yield ();
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ();
}
System.out.println ("执行结束自动释放锁");
}
}).start ();
new Thread (() -> {
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ();
}
synchronized (lock) {
System.out.println ("线程二获得了ThreadTest对象锁");
}
}).start ();
}
public static void main(String[] args) {
test ();
}
}
控制台输出:
线程一获得了ThreadTest对象锁
执行结束自动释放锁
线程二获得了ThreadTest对象锁
分析:由于线程一率先获得了对象锁,执行yield方法,线程一任然处于可执行状态,但yield方法并不释放锁,所以线程二还是处于阻塞状态,等待线程一执行完毕,线程二获得对象锁,线程二执行同步代码块。
5.join()方法:
当主线程开启一个或多个子线程的时候,使用join方法,必须等该线程运行结束,主线程或其他子线程才由阻塞状态转为可执行状态。
import java.util.concurrent.TimeUnit;
public class ThreadTest {
private static ThreadTest lock = new ThreadTest ();
public static void test() {
Thread t1 = new Thread (() -> {
for (int i = 0; i < 5; i++) {
System.out.println ("A----" + i);
}
});
Thread t2 = new Thread (() -> {
for (int j = 0; j < 5; j++) {
System.out.println ("B----" + j);
}
});
t1.start ();
try {
t1.join ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
t2.start ();
}
public static void main(String[] args) {
test ();
}
}
控制台输出:
A----0
A----1
A----2
A----3
A----4
B----0
B----1
B----2
B----3
B----4
分析:主线程开始运行t1线程,接着join方法被调用,主线程处于阻塞状态,等待t1线程执行完毕,主线程状态变为可运行状态,t2线程被执行。
6.总结:以上是Java线程唤醒和阻塞的五种常用方法,不同的方法有不同的特点,其中wait() 和 notify()是其中功能最强大、使用最灵活的方法,但这也导致了它们效率较低、较容易出错的特性,因此,在实际应用中应灵活运用各种方法,以达到期望的目的与效果
注意:本文归作者所有,未经作者允许,不得转载