yield,sleep,wait,notify,notifyAll,suspend,stop,resume等方法区分

327 阅读8分钟

说明

线程状态的转换图:

yield

让出时间片,但是不会释放锁

public class SynchronizedTest {
    public synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "抢占1方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
        try {
            Thread.yield();
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放1方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }
}
public class Test2 {
    public static void main(String[] args) throws ParseException {
        SynchronizedTest s1 = new SynchronizedTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-04 10:05:54
线程1释放1方法2020-08-04 10:05:59
线程2抢占1方法,时间:2020-08-04 10:05:59
线程2释放1方法2020-08-04 10:06:04

sleep

线程进入睡眠状态,不会释放锁

public class SynchronizedTest {
    public synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "抢占1方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放1方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }
}

public class Test2 {
    public static void main(String[] args) throws ParseException {
        SynchronizedTest s1 = new SynchronizedTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-04 10:07:12
线程1释放1方法2020-08-04 10:07:17
线程2抢占1方法,时间:2020-08-04 10:07:17
线程2释放1方法2020-08-04 10:07:22

wait

调动方法之前,必须要持有锁。调用了wait()方法以后,锁就会被释放,进入锁的等待队列,方法返回后重新拿到锁.对于wait说明几个点:

  1. wait方法会失去锁
  2. wait可以被其他线程唤醒
  3. wait如果配置了时间参数,时间到了以后可以自动唤醒
public class SynchronizedTest {
    public void method1() {
        try {
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占1方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.wait();
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放1方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }
}
public class Test2 {
    public final static Object lock = new Object();

    public static void main(String[] args) throws ParseException {
        SynchronizedTest s1 = new SynchronizedTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-05 10:49:51
线程2抢占1方法,时间:2020-08-05 10:49:51

自动唤醒代码

public class SynchronizedTest {
    public void method1() {
         try {
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占1方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.wait(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放1方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }
}
public class Test2 {
    public static void main(String[] args) throws ParseException {
        SynchronizedTest s1 = new SynchronizedTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-05 10:52:45
线程2抢占1方法,时间:2020-08-05 10:52:45
线程1释放1方法2020-08-05 10:52:50
线程2释放1方法2020-08-05 10:52:50

notify

调动方法之前,必须要持有锁,调用notify()方法本身不会释放锁的。而是通知等待队列中的某一个线程,同步代码块执行完毕后才会释放锁 需要注意的是,下面的代码中Test2.lock.notify()这一行代码需要放在synchronized内部,否则会抛出异常。因为前面提过了,调动notify之前需要先持有锁。需要说明的是notify唤醒的是指定锁的等待队列中的某一个线程,其他锁的等待队列无法唤醒。下面的例子中唤醒的就是另个线程中的一个。

public class SynchronizedTest {

    public void method1() {
        try {
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占1方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.wait();
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放1方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }

    public void method2() {
        try {
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占2方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.wait();
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放2方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }

    public void method3() {
        try {
            Thread.sleep(5000);
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占3方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.notify();
                System.out.println(Thread.currentThread().getName() + "唤醒其他线程,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放3方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }
}
public class Test2 {
    public final static Object lock = new Object();

    public static void main(String[] args) throws ParseException {
       SynchronizedTest s1 = new SynchronizedTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        Thread thread3 = new Thread(s1::method3);
        thread3.setName("线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-05 10:59:28
线程2抢占1方法,时间:2020-08-05 10:59:28
线程3抢占3方法,时间:2020-08-05 10:59:33
线程3唤醒其他线程,时间:2020-08-05 10:59:33
线程3释放3方法2020-08-05 10:59:33
线程1释放1方法2020-08-05 10:59:38

notifyAll

同notify,有一点不同在于,notifyAll会发出n个信号(n=等待线程数),而notify只会发出一个信号,通常情况下,尽量选择notifyAll

public class SynchronizedTest {

    public void method1() {
        try {
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占1方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.wait();
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放1方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }

    public void method2() {
        try {
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占2方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.wait();
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放2方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }

    public void method3() {
        try {
            Thread.sleep(5000);
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占3方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Test2.lock.notifyAll();
                System.out.println(Thread.currentThread().getName() + "唤醒其他线程,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "释放3方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }
}
public class Test2 {
    public final static Object lock = new Object();

    public static void main(String[] args) throws ParseException, InterruptedException {
        SynchronizedTest s1 = new SynchronizedTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        Thread thread2 = new Thread(s1::method2);
        thread2.setName("线程2");
        Thread thread3 = new Thread(s1::method3);
        thread3.setName("线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-04 20:48:42
线程2抢占2方法,时间:2020-08-04 20:48:42
线程3抢占3方法,时间:2020-08-04 20:48:47
线程3唤醒其他线程,时间:2020-08-04 20:48:47
线程3释放3方法2020-08-04 20:48:47
线程2释放2方法2020-08-04 20:48:52
线程1释放1方法2020-08-04 20:48:57

stop

stop方法会停止线程,同时会释放锁,但是目前已经不推荐使用stop方法,因为它是不安全的,会导致资源释放不完全。类似下面的例子,假如代码中不是打印日志,而是一个相对原子的操作,比如给两个参数赋值,x=3,y=4.那么假如程序刚运行x=3的时候就stop了,这时候后面的赋值也没有完成,等于是不完整的。所以现在都不在使用stop去停止线程的操作。

public class StopTest {

    public void method1() {
        try {
            synchronized (Test2.lock) {
                System.out.println(Thread.currentThread().getName() + "抢占1方法,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName() + "释放1方法" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Test3 {
    public static void main(String[] args) throws ParseException, InterruptedException {
        StopTest s1 = new StopTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        thread1.start();

        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        thread2.start();

        Thread.sleep(1000);
        thread1.stop();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-05 15:36:29
线程2抢占1方法,时间:2020-08-05 15:36:30
线程2释放1方法2020-08-05 15:36:35

suspend

还是上述的代码结构,只不过长城调用suspend,会发现程序一直阻塞在那里,因为锁未释放,线程2一直在等待这个锁。 因为suspend调用的时候不会释放锁,所以很容易造成死锁问题。这也是suspend不推荐使用的原因。

public class Test3 {
    public static void main(String[] args) throws ParseException, InterruptedException {
        StopTest s1 = new StopTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        thread1.start();

        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        thread2.start();

        Thread.sleep(1000);
        thread1.suspend();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-05 15:36:29

resume

resume针对的就是调用了suspend而暂停的线程,可以唤醒这些线程继续执行。还是上述的代码结构:

public class Test3 {
    public static void main(String[] args) throws ParseException, InterruptedException {
        StopTest s1 = new StopTest();
        Thread thread1 = new Thread(s1::method1);
        thread1.setName("线程1");
        thread1.start();

        Thread thread2 = new Thread(s1::method1);
        thread2.setName("线程2");
        thread2.start();

        Thread.sleep(3000);
        System.out.println("开始暂停线程1"+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
        thread1.suspend();

        Thread.sleep(3000);
        System.out.println("开始唤醒线程1"+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
        thread1.resume();
    }
}
  • 打印结果
线程1抢占1方法,时间:2020-08-05 15:51:25
开始暂停线程12020-08-05 15:51:28
开始唤醒线程12020-08-05 15:51:31
线程1释放1方法2020-08-05 15:51:31
线程2抢占1方法,时间:2020-08-05 15:51:31
线程2释放1方法2020-08-05 15:51:36