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弃用)
suspend()
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方法会先释放对象锁,线程二成功获得锁,执行同步代码块。
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()方法只能使同优先级的线程有执行的机会。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知,不会释放锁)
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方法,必须等该线程运行结束,主线程或其他子线程才由阻塞状态转为可执行状态。
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()是其中功能最强大、使用最灵活的方法,但这也导致了它们效率较低、较容易出错的特性,因此,在实际应用中应灵活运用各种方法,以达到期望的目的与效果