小记:JUC的8锁理论

732 阅读7分钟

先了解一个概念:synchronized 锁的是这个方法所在的资源类,就是这个对象,也就是同一时间段不可能有两个线程同时进到这个资源类,同一时间段,只允许有一个线程访问资源类里面的其中一个synchronized 方法

编写一个资源类有两个方法,然后重复测试,主方法有两个线程对资源类进行操作,下面对资源类进行编写,查看调用情况,为了查看方便,两个方法之前间隔100毫秒

T1:标准访问

class Phone{

    public synchronized void sendEmail(){
        System.out.println("send Email~~~");
    }
    
    public synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone.sendMsg(); },"B").start();
    }
}

结果:虽然先执行的sendEmail,是因为中间 Thread.sleep(100);,但其实是不一定的,看cpu的调度,被 synchronized 修饰的方式,锁的对象是方法的调用者,因为这两个方法锁的是同一个对象,所以先调用的先执行。

T2:邮件方法暂停3秒

class Phone{
    public  synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send Email~~~");
    }
    
    public synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone.sendMsg(); },"B").start();
    }
}

因为synchronized为对象锁 对象锁

  • 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
  • 其他的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法,
  • 锁的是当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他的synchronized方法

T3:新增一个普通方法hello()

class Phone{
    public  synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send Email~~~");
    }

    public synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }

    public void hello(){
        System.out.println("hello world!");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone.hello(); },"B").start();
    }
}

邮件方法先执行,并且等待3秒,这次线程B没有等待邮件方法执行完才打印,而是直接打印,因为hello没有被synchronized 修饰

  • 普通方法和同步锁无关

T4:两部手机

class Phone{
    public  synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send Email~~~");
    }

    public synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }

    public void hello(){
        System.out.println("hello world!");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone2.sendMsg(); },"B").start();
    }
}

先打印的phone2的sendMsg,3s后再打印phone的sendemail,因为资源对象是不同的。

  • 换成两个对象后,不是同一个实例,就不是同一把锁了,情况立刻变化,两方互不相关

T5:两个静态同步方法,同一部手机

class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send Email~~~");
    }

    public static synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }

    public void hello(){
        System.out.println("hello world!");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone.sendMsg(); },"B").start();
    }
}

3s过后先打印sendEmail,接着打印sendMsg。

T6:两个静态同步方法,两部手机

class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send Email~~~");
    }

    public static synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }

    public void hello(){
        System.out.println("hello world!");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone2.sendMsg(); },"B").start();
    }
}


依旧是先打印邮件

结合T5,T6,静态同步方法是类锁,不再是对象锁,即锁的是Phone.class,锁的同一个字节码对象

synchronized实现同步的基础:java中的每一个对象(实例)都可以作为锁,具体表现为以下三种形式。

  • 1、对于普通同步方法,锁是当前实例对象,锁的是当前对象this,
  • 2、对于同步方法块,锁的是synchronized括号里配置的对象。
  • 3、对于静态同步方法,锁是当前类的class对象

T7:一个静态同步方法,一个普通同步方法,同一部手机

 class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send Email~~~");
    }

    public  synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }

    public void hello(){
        System.out.println("hello world!");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone.sendMsg(); },"B").start();
    }
}

先打印sendMsg,因为sendEmail锁的是模板的(加了static),sendMsg锁的是类的,两者是不冲突的。

T8:一个静态同步方法,一个普通同步方法,二部手机

class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send Email~~~");
    }

    public  synchronized void sendMsg(){
        System.out.println("send Msg~~~");
    }

    public void hello(){
        System.out.println("hello world!");
    }
}

public class _八锁理论 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        //1
        new Thread(()->{ phone.sendEmail(); },"A").start();
        Thread.sleep(100);
        //2
        new Thread(()->{ phone2.sendMsg(); },"B").start();
    }
}

一样是先打印sendMsg,因为sendEmail锁的是模板的(加了static),sendMsg锁的是模板的实例对象的,而且操作的是不同的对象,两者是不冲突的。

总结

synchronized实现同步基础,Java中的每一个对象都可以作为锁。
具体的表现形式有下面的三种:

  • 1:对于普通方法而言,锁是当前实例对象。
  • 2:对于静态同步方法,锁是当前类的class对象。
  • 3:对于同步方法块而言,锁是synchronized括号里配置的对象。 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

也就是说当一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁。如果别的实例对象的非静态同步方法和和该实例对象的非静态同步方法用的是不同的锁,那么就不用等待该实例对象已获取的锁的非静态同步方法释放锁,直接获取他们自己的锁。

对象锁和类锁(this/class)是两个不同的对象,所以静态同步方法和非静态同步方法之间是不会有竞争条件的。但一旦静态方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。

结论

  • 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
  • 也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁,
  • 可是别的实例对象的普通同步方法因为跟该实例对象的普通同步方法用的是不同的锁,
  • 所以无需等待该实例对象已获取锁的普通同步方法释放锁就可以获取他们自己的锁。 静态同步锁 与 普通同步锁 互相不干扰
  • 所有的静态同步方法用的也是同一把锁--类对象本身,
  • 这两把锁(this/class)是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有静态条件的。
  • 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。
  • 而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个类的实例对象