首先感谢下 B站的狂神,在这里第一次听到“8锁现象”,并弄懂了“锁到底锁的是谁”。
8锁现象,实际对应的就是8个问题。
掌握了这8个问题后:可以清楚判断锁的是谁!知道什么是锁,锁到底锁的是谁!
锁方法的调用者
问题一
如下代码:标准情况下,下边两个线程先打印 发短信 还是 打电话 ?
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{phone.seedMsg();}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone.call();}, "B").start();
}
}
class Phone{
public synchronized void seedMsg(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:先输出发短信,再输出打电话。
思考下为什么?
如果你是认为线程A先被调用的,那是不对的。现在抱着这个想法,再去思考下面的代码:
问题二
如下代码:将发短信的方法sleep四秒,两个线程先打印 发短信 还是 打电话 ?
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{phone.seedMsg();}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone.call();}, "B").start();
}
}
class Phone{
public synchronized void seedMsg(){
try {TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:还是先输出发短信,再输出打电话。
思考下为什么?
首先知道 锁住的对象是谁
?因为 synchronized 加在方法上,所以锁的对象是方法的调用者
。如上示例:两条线程执行的方法调用者是不是都是new出来的phone 对象啊,所以两个方法用的是同一个锁,谁先拿到谁先执行!
通俗解释:
phone对象就是方法的调用者,也就是手机,它可以打电话和发短信。
现在有两个人线程A 和线程B,他们一个想打电话,一个想发短信。
线程A,先拿到锁(也就是手机),抱着锁(手机)睡了4秒。 线程B肯定拿不到锁(手机),需要等待。所以最终A睡了4秒醒了就执行输出了发短信,让后释放掉锁,线程B才能拿到锁执行输出打电话
问题三
如下代码:Phone类中新增一个普通方法hello(),线程B调用,那么两个线程先打印 发短信 还是 先输出hello?
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{ phone.seedMsg();}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone.hello();}, "B").start();
}
}
// 手机
class Phone2{
//同步方法
public synchronized void seedMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//同步方法
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:先输出了hello,再输出发短信。
思考下为什么?
因为hello()方法,是普通方法,根本不受锁的影响。线程B执行hello方法,不需要获取锁就能执行,所以也不必等待A执行完释放锁。
问题四
如下代码:创建两个phone对象,线程调用不同对象的方法,那么两个线程先打印 发短信 还是 打电话?
public class Test2 {
public static void main(String[] args) {
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
// 线程A,
new Thread(()->{phone1.seedMsg();}, "A").start();
// 1秒延迟
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B,人
new Thread(()->{ phone2.call();}, "B").start();
}
}
// 手机
class Phone2{
//同步方法
public synchronized void seedMsg(){
// 4秒延迟
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//同步方法
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:先输出了打电话,再输出发短信。
思考下为什么?
还是那句话,synchronized用在方法上,那么锁的是方法的调用者
。现在有两个调用者,就相当于你用你的手机发短信,我用我的手机打电话,所以互不影响啊。
锁class对象
问题五
如下代码:将方法变为静态同步方法,那么两个线程先打印 发短信 还是 打电话?
public class Test3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
// 线程A,
new Thread(()->{phone.seedMsg();}, "A").start();
// 1秒延迟
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B,
new Thread(()->{phone.call();}, "B").start();
}
}
// 手机
class Phone3{
// 静态同步方法
public static synchronized void seedMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 静态同步方法
public static synchronized void call(){
System.out.println("打电话");
}
}
执行结果:先输出发短信,再输出打电话。
思考下为什么?
如果你的理解还是“synchronized用在方法上,那么锁的是方法的调用者”,那么就错了。别忘了方法用static
修饰了。
static静态方法,在类初始化
的时候,就加载到内存中生成了class
对象。要知道java中所有的类,只有一个class对象,
这个class对象是唯一的,所以锁静态方法,其实锁的就是class对象。
问题六
如下代码:方法还是静态同步方法,但是我们创建两个对象来调方法,那么两个线程先打印 发短信 还是 打电话?
public class Test3 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{phone1.seedMsg();}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call();}, "B").start();
}
}
// 手机
class Phone3{
// 静态同步方法
public static synchronized void seedMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 静态同步方法
public static synchronized void call(){
System.out.println("打电话");
}
}
执行结果:先输出发短信,再输出打电话。
上边已经说了,锁静态方法,其实锁的就是class对象,java中所有的类,只有一个class对象
,所以无论几个对象,使用的锁都是一样的。
同时锁方法的调用者和class对象
问题七
如下代码:一个是静态同步方法,一个普通同步方法,那么两个线程先打印 发短信 还是 打电话?
public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
new Thread(()->{phone1.seedMsg();}, "A").start();
// 1秒延迟
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B,人
new Thread(()->{phone1.call();}, "B").start();
}
}
// 手机
class Phone4{
// 静态同步方法
public static synchronized void seedMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 普通同步方法
public synchronized void call(){
System.out.println("打电话");
}
}
执行结果:先输出打电话,再输出发短信。
思考下为什么?
这还用想?两个锁,锁的东西都不一样啊,一个锁class对象,一个锁Phone4 对象,两个都是互不影响的。
问题八
如下代码:在上面的基础上,增加一个对象,两个对象分别调用静态和非静态的同步方法,那么两个线程先打印 发短信 还是 打电话?
public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{phone1.seedMsg();}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone2.call();}, "B").start();
}
}
// 手机
class Phone4{
// 静态同步方法
public static synchronized void seedMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 普通同步方法
public synchronized void call(){
System.out.println("打电话");
}
}
相信到这里已经能肯定回答出“先输出打电话,再输出发短信了”。
不要怀疑,以为增加了一个对象,就会有什么影响?只要记住静态同步方法锁class对象,普通同步方法锁调用者
就行。两个锁 锁住的东西不一样,所以互不影响的。