JUC学习笔记(8锁问题(对象锁、类锁区别))

206 阅读5分钟

8锁问题(对象锁、类锁)

1.初始场景

  • Phone资源类有 void sendMessage() 、void call()两个方法
  • main方法开启 多线程场景 进行测试,8种情况可以提出8个问题,即8锁问题
  • JUC下线程sleep方法:imeUnit.SECONDS.sleep(1);

public class Demo1 {


    public static void main(String[] args) {
        
    }
}


class Phone {

    public synchronized void sendMessage(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

##### 1.创建一个Phone实例多线程调用两个方法,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo1 {


    public static void main(String[] args) {

        Phone p = new Phone();

        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            p.call();
        }).start();

    }
}


class Phone {

    public synchronized void sendMessage(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话



  • 因为synchronized关键字 是对 该资源类的对象 上锁,因此哪个线程先拿到对象锁,就先执行
  • 因此上面的线程先拿到锁,先执行,如果还不理解接着看问题2
2.创建一个Phone实例多线程调用两个方法,其中第一个线程调用的方法中加延迟,问哪一个先执行?

import java.util.concurrent.TimeUnit;

public class Demo1 {


    public static void main(String[] args) {

        Phone p = new Phone();

        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            p.call();
        }).start();

    }
}


class Phone {

    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话

结果分析

  • 原理同上。还是上面的线程先拿到 资源类 锁对象
  • 同一资源类下,synchronized修饰的方法 使用的是当前资源类 对象锁。谁先拿到谁执行。
3.创建一个Phone实例多线程调用两个方法,其中一个是普通方法,而且该线程位置靠后,问哪一个先执行?

import java.util.concurrent.TimeUnit;

public class Demo3 {


    public static void main(String[] args) {

        Phone3 p = new Phone3();

        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            p.watchMovie();
        }).start();

    }
}


class Phone3 {

    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

    public void watchMovie(){
        System.out.println("看电影");
    }
}

输出:
看电影
发短信

结果分析

  • watchMovie()普通方法,不受同步方法的影响,也就是不受 锁 的影响
  • 因为 sendMessage()方法体 中有延迟语句,因此会后输出,其实 抛去延迟来说,两个输出结果为不一定
4.创建两个Phone实例多线程调用两个方法,问哪一个先执行?

import java.util.concurrent.TimeUnit;

public class Demo4 {


    public static void main(String[] args) {

        Phone4 one = new Phone4();
        Phone4 two = new Phone4();

        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            two.call();
        }).start();

    }
}


class Phone4 {

    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }


}
输出:
打电话
发短信

结果分析

  • 区别于问题1,该情况是 两个资源类对象分别开启两个线程,因此锁对象 并无互相干扰,因为线程延时的原因,打电话 先输出
  • 由此可在次证明synchronized加载方法前面锁住的是 调用该方法的 实例对象,多个实例之间的锁 不干扰
5.创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?

import java.util.concurrent.TimeUnit;

public class Demo5 {


    public static void main(String[] args) {

        Phone5 one = new Phone5();


        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            one.call();
        }).start();

    }
}


class Phone5 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized static void call(){
        System.out.println("打电话");
    }


}

输出:
发短信
打电话

问题分析

  • 加上static关键字之后,两个方法都变为静态方法。
  • 发短信 在前面的原因是 synchronized 加 静态方法 锁的是 Class ,Phone5.Class只有单个。因此第二个线程需要 等待第一个线程释放Class锁才能执行。
6.创建两个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?

import java.util.concurrent.TimeUnit;

public class Demo6 {


    public static void main(String[] args) {

        Phone6 one = new Phone6();
        Phone6 two = new Phone6();


        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            two.call();
        }).start();

    }
}


class Phone6 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized static void call(){
        System.out.println("打电话");
    }


}

输出:
发短信
打电话

结果分析

  • 两个对象的Class只有一个Phone6.Class
  • 原理同5,synchronized 加 静态方法 锁的是 Class
7.创建一个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?

import java.util.concurrent.TimeUnit;

public class Demo7 {


    public static void main(String[] args) {

        Phone7 one = new Phone7();



        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            one.call();
        }).start();

    }
}


class Phone7 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized  void call(){
        System.out.println("打电话");
    }


}

输出:
打电话
发短信

结果分析

  • 打电话 先输出的原因是 Phone7 实例 和 Phone7.Class 分别被锁,两个线程之间并无影响,因为线程延迟的原因。
  • 再次 证明 synchronized 锁的是 类实例即对象 、synchronized 加 静态方法 锁的是 Class
8.创建两个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?

import java.util.concurrent.TimeUnit;

public class Demo7 {


    public static void main(String[] args) {

        Phone8 one = new Phone8();
        Phone8 two = new Phone8();



        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            two.call();
        }).start();

    }
}


class Phone7 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized  void call(){
        System.out.println("打电话");
    }


}
输出:
打电话
发短信

结果分析

  • 原理同7。两个线程分别锁的是 Phone8.Class 和 Phone8实例,因为线程延迟,打电话 才会优先输出。