说明
线程状态的转换图:
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说明几个点:
- wait方法会失去锁
- wait可以被其他线程唤醒
- 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