Version: Next

八锁现象彻底理解锁——到底锁了啥

锁只会锁两种东西

  1. 对象 (可能有多个)
  2. Class模板对象 (唯一)

八锁,就是关于锁的八个问题

1、synchronized修饰方法,锁的是方法的调用者

下面这段例子中:

  • sendMsg()call()使用了synchronized修饰
  • 创建一个唯一的phone对象,调用这两个方法,那么锁的就是phone对象
  • 两个方法使用的是同一把锁,谁先拿到,谁先执行
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
},"A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendMsg(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
发短信
打电话

执行结果总是,先“发短信”,再“打电话“

2、让一个同步方法沉睡

直观的感受是,因为发短信这个方法先被调用,所以结果会这样,那么在发短信方法中再睡一会儿,观察一下,结果还是不变的

public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
},"A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
发短信
打电话

出现这样的结果,是因为synchronized的存在,锁住了调用方法的对象,phone

3、设置一个非synchronized方法

定义一个不用synchronized修饰的方法,在第二个线程中,不再调用打电话方法,而是调用这个没有用synchronized修饰的方法

public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
class Phone2 {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
public void hello() {
System.out.println("hello");
}
}
hello
发短信

主线程睡了一秒,然后打出了"hello";发短信方法睡了4秒,打出了"发短信"

4、设置两个资源对象

如果有两个phone对象

public class Test3 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(() -> {
phone1.sendMsg();
}, "A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone3 {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
  • 执行之后,首先进入发短信方法,开始睡4秒;主线程睡了1秒后进入打电话方法,打出“打电话”,此时发短信已经睡了1秒,于是又过了3秒,打出“发短信”
  • 两个synchronized,锁都不是同一个phone对象
打电话
发短信

5、设置两个静态同步方法

静态同步方法,锁的不再是资源对象,而是锁Class反射对象,Phone.class对象,全局唯一

public class Test4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone4 {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}

睡4秒,然后打出"发短信、打电话"

发短信
打电话

6、两个静态同步方法 + 两个资源对象

如果在这种情况下,设置两个资源对象

public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(() -> {
phone1.sendMsg();
}, "A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone4 {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}

依然是睡4秒,然后打印出“发短信、打电话”,因为静态同步方法,就不再锁资源对象,而是锁资源类的Class对象

发短信
打电话

7、一个静态同步方法 + 一个普通同步方法

public class Test5 {
public static void main(String[] args) {
Phone5 phone = new Phone5();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone5 {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}

一个锁的是Class对象,一个锁的是phone实例化对象,不是同一把锁

  • 主线程睡1秒,打出“打电话”
  • 再睡3秒后,打出“发短信”
打电话
发短信

8、一个静态同步方法 + 一个普通同步方法 + 两个资源对象

设置两个资源对象

public class Test5 {
public static void main(String[] args) {
Phone5 phone1= new Phone5();
Phone5 phone2= new Phone5();
new Thread(() -> {
phone1.sendMsg();
}, "A").start();
try { // 用JUC的TimeUnit使线程睡1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone5 {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}

一个锁的是类的Class对象,另一个锁的是调用者实例化对象phone2,不是同一把锁

  • 主线程睡1秒,打出“打电话”
  • 发短信方法已经睡了1秒,再睡3秒,打出“发短信”
打电话
发短信