一、sleep & wait
首先从一个面试问题开篇:我们经常用的sleep()
& wait()
方法有什么不同?
基于我自己的知识范围,我给出以下回答:
sleep
方法是Thread
类的静态方法,而wait
方法是Object
类下的非静态方法sleep
方法 执行时不会释放monitor;而wait
方法在执行时会释放monitor,并将当前线程加入到等待monitor的wait set
中。- 啥时候想让当前线程休眠了,直接
Thread.sleep(x)
就好,不需要依赖monitor;但wait
需要- 通常情况下,
sleep
方法的执行线程不需要别人唤醒,但wait()
通常需要别人notify()
后才能唤醒接着执行,当然wait(x)
方法除外。
对于第二点,通过以下代码进行验证。
关于sleep,我们常规的sleep写法:
public static void main(String[] args) {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
此时通过jstack查看线程状态发现:[main线程并没有在等待monitor]
"main" #1 prio=5 os_prio=31 tid=0x00007fe24d002000 nid=0x1903 waiting on condition [0x0000700006f50000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
关于wait,我们通过以下代码来对wait方法进行说明:
public static void main(String[] args) {
Stream.of("T1", "T2").forEach(name ->
new Thread(name) {
@Override
public void run() {
// m1();
m2();
}
}.start()
);
}
public static void m1() {
synchronized (LOCK) {
try {
System.out.println("The Thread " + Thread.currentThread().getName() + " enter.");
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void m2() {
synchronized (LOCK) {
try {
System.out.println("The Thread " + Thread.currentThread().getName() + " enter.");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
首先注掉m1()
,放开m2()
。运行程序,得到运行结果:
The Thread T1 enter.
The Thread T2 enter.
这就说明,在T1获得LOCK进入synchronized代码块之后,通过wait将锁进行了释放,使得T2尝试获取LOCK时能够成功获取,并进入synchronzied代码块执行了println语句。
那如果注掉m2()
放开m1()
呢?结果如下:
The Thread T1 enter.
通过jstack发现,由于T1手里握着锁,仍处在synchronized代码块中,因此T2在尝试获取锁时因为无法获取而进入阻塞状态:
"T2" #14 prio=5 os_prio=31 tid=0x00007fd96902a000 nid=0x5b03 waiting for monitor entry [0x000070000be19000]
java.lang.Thread.State: BLOCKED (on object monitor)
"T1" #13 prio=5 os_prio=31 tid=0x00007fd968895000 nid=0xa903 waiting on condition [0x000070000bd16000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
关于第三点,做以下测试,即去掉获取锁的代码为:
public static void m2() {
// synchronized (LOCK) {
try {
System.out.println("The Thread " + Thread.currentThread().getName() + " enter.");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
执行结果:
Exception in thread "T1" Exception in thread "T2" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
所以结论为:需要先获取锁才能执行wait(包括notify、notifyAll等)操作
二、synchronized
通过以上代码我们注意到,synchronized使用的是一个实例化之后的Object类的对象LOCK作为锁,那就聊聊关于synchronized的几种写法,以及这几种写法分别把锁加到了哪里。
① 同步代码块
final Object LOCK = new Object();
synchronized (LOCK) {
try {
System.out.println("The Thread " + Thread.currentThread().getName() + " enter.");
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这种写法就是很明显的用LOCK
对象作为锁用于线程同步
② 同步方法
被synchronized所修饰的方法的所在类:
public class SychronizedStatic {
public synchronized void m4() {
System.out.println("m4 " + Thread.currentThread().getName());
try {
Thread.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void m5() {
System.out.println("m5 " + Thread.currentThread().getName());
try {
Thread.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试代码:
public static void main(String[] args) {
SychronizedStatic sychronizedStatic = new SychronizedStatic();
new Thread("T4") {
@Override
public void run() {
sychronizedStatic.m4();
}
}.start();
new Thread("T5") {
@Override
public void run() {
sychronizedStatic.m5();
}
}.start();
}
这种写法也比较好理解,作为锁的对象就是调用者,即sychronizedStatic
。
③ 静态同步方法
被synchronized所修饰的静态方法的所在类:
public class SychronizedStatic {
public synchronized static void m1() {
System.out.println("m1 " + Thread.currentThread().getName());
try {
Thread.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void m2() {
System.out.println("m2 " + Thread.currentThread().getName());
try {
Thread.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void m3() {
synchronized (SychronizedStatic.class) {
System.out.println("m3 " + Thread.currentThread().getName());
try {
Thread.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试方法:
public static void main(String[] args) {
new Thread("T1") {
@Override
public void run() {
SychronizedStatic.m1();
}
}.start();
new Thread("T2") {
@Override
public void run() {
SychronizedStatic.m2();
}
}.start();
}
测试结果如下:
m1 T1
这就说明,m2()
和m3()
阻塞在了SychronizedStatic.class
上,也就是说被synchronized修饰的静态方法,在做线程同步时,加锁是加在静态方法所在的静态类Class对象上的。
三、 瞎扯
sleep & wait & synchronize,这都是java开发最基本的东西,但是如果稍微细问问的话,还真不一定理解的那么透彻。因此,学无止境,要融会贯通~