JUC学习之线程8锁

一、简介

所谓线程八锁,其实就是就是线程锁的8种情况,对应于是否加上synchronized,是否加上static等8种常见情况,具体见下面详解。

二、线程8锁

  • 第一种情况: 两个线程调用同一个对象的两个普通同步方法,先打印one还是two?
/**
 * 线程八锁第一种情况: 两个线程调用同一个对象的两个普通同步方法,先打印one还是two?
 * <p>
 * 运行结果: 可能先打印one,也可能先打印two
 * <p>
 * 解释:
 * 因为调用start()方法启动线程的时候,具体是由CPU调度获取执行权,所以严格来说顺序不一定.
 * 被synchronized修饰的方法,锁的对象是方法的调用者shareResource。因为两个方法的调用者是同一个,所以两个方法用的是同一个锁,先调用方法的先执行,具体由CPU调度决定.
 */
public class Test8LockDemo {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(shareResource::one).start();
        new Thread(shareResource::two).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public synchronized void one() {
        System.out.println("one...");
    }

    public synchronized void two() {
        System.out.println("two...");
    }

}

运行结果:

one...
two...

解释:

因为调用start()方法启动线程的时候,具体是由CPU调度获取执行权,所以严格来说顺序不一定.
被synchronized修饰的方法,锁的对象是方法的调用者shareResource。因为两个方法的调用者是同一个,所以两个方法用的是同一个锁,先调用方法的先执行,具体由CPU调度决定.
  • 第二种情况: 给one()方法加一点睡眠暂停,先打印one还是two?
/**
 * 线程八锁第二种情况: 给one()方法加一点睡眠暂停,先打印one还是two?
 * <p>
 * 运行结果: 经过5秒后先打印one,再打印two
 * <p>
 * 解释:
 * TimeUnit.SECONDS.sleep方法不会释放对象锁,所以锁的是同一个对象shareResource,
 * 某个时刻只能有一个线程进入到同一个对象锁的同步方法内,所以two方法必须等有第一个方法执行完释放锁之后才能执行.
 */
public class Test8LockDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareResource shareResource = new ShareResource();
        new Thread(shareResource::one).start();
        //这里加一点延迟,为了保证前面的线程先获取执行权.
        Thread.sleep(1000);
        new Thread(shareResource::two).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public synchronized void one() {
        //one()方法睡眠5秒
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("one...");
    }

    public synchronized void two() {
        System.out.println("two...");
    }

}

运行结果:

one...
two...

解释:

TimeUnit.SECONDS.sleep方法不会释放对象锁,所以锁的是同一个对象shareResource,
某个时刻只能有一个线程进入到同一个对象锁的同步方法内,所以two方法必须等有第一个方法执行完释放锁之后才能执行.
  • 第三种情况: 新增一个普通方法three(),先打印one还是three?
/**
 * 线程八锁第三种情况: 新增一个普通方法three(),先打印one还是three?
 * <p>
 * 运行结果: 先打印three...,过五秒后打印one...
 * <p>
 * 解释:
 * three()是普通方法,执行的时候无需判断是否拥有锁对象,不受锁的迎新,所以不需要等待直接执行,故先打印three...
 */
public class Test8LockDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareResource shareResource = new ShareResource();
        new Thread(shareResource::one).start();
        //这里加一点延迟,为了保证前面的线程先获取执行权.
        Thread.sleep(1000);
        new Thread(shareResource::three).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public synchronized void one() {
        //one()方法睡眠5秒
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("one...");
    }

    public synchronized void two() {
        System.out.println("two...");
    }

    public void three() {
        System.out.println("three...");
    }

}

运行结果:

three...
one...

解释:

three()是普通方法,执行的时候无需判断是否拥有锁对象,不受锁的迎新,所以不需要等待直接执行,故先打印three...
  • 第四种情况: 两个普通同步方法,两个ShareResource对象,先打印one还是two
/**
 * 线程八锁第四种情况: 两个普通同步方法,两个ShareResource对象,先打印one还是two
 * <p>
 * 运行结果: 先打印two...,经过5秒后打印one...
 * <p>
 * 解释:
 * 由于有两个对象shareResource和shareResource2,分别调用各自的同步方法,被synchronized修饰的方法,
 * 锁的对象是方法的调用者,所以各自锁定自己的对象,两个方法的调用者不是同一个,所以两个方法用的不是同一个锁,
 * 两者互不影响,由于one()方法有延时,所以先打印two...
 */
public class Test8LockDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareResource shareResource = new ShareResource();
        ShareResource shareResource2 = new ShareResource();
        new Thread(shareResource::one).start();
        //这里加一点延迟,为了保证前面的线程先获取执行权.
        Thread.sleep(1000);
        new Thread(shareResource2::two).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public synchronized void one() {
        //one()方法睡眠5秒
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("one...");
    }

    public synchronized void two() {
        System.out.println("two...");
    }

}

运行结果:

two...
one...

解释:

由于有两个对象shareResource和shareResource2,分别调用各自的同步方法,被synchronized修饰的方法,
锁的对象是方法的调用者,所以各自锁定自己的对象,两个方法的调用者不是同一个,所以两个方法用的不是同一个锁,
两者互不影响,由于one()方法有延时,所以先打印two...
  • 第五种情况: 两个静态同步方法,一个ShareResource对象,先打印one还是two
/**
 * 线程八锁第五种情况: 两个静态同步方法,一个ShareResource对象,先打印one还是two
 * <p>
 * 运行结果: 先打印one...,后打印two...
 * <p>
 * 解释:
 * 被static synchronized修饰相当于synchronized(Test8LockDemo.class) { xxx },静态同步方法锁定的是这个类Class对象,也即是整个类的模板都被锁住了。
 * 所以两个方法用的是同一个锁对象,所以先打印one...
 */
public class Test8LockDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> shareResource.one()).start();
        //这里加一点延迟,为了保证前面的线程先获取执行权.
        Thread.sleep(1000);
        new Thread(() -> shareResource.two()).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public static synchronized void one() {
        //one()方法睡眠5秒
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("one...");
    }

    public static synchronized void two() {
        System.out.println("two...");
    }

}

运行结果:

one...
two...

解释:

被static synchronized修饰相当于synchronized(Test8LockDemo.class) { xxx },静态同步方法锁定的是这个类Class对象,也即是整个类的模板都被锁住了。
所以两个方法用的是同一个锁对象,所以先打印one...
  • 第六种情况: 两个静态同步方法,两个ShareResource对象,先打印one还是two
/**
 * 线程八锁第六种情况: 两个静态同步方法,两个ShareResource对象,先打印one还是two
 * <p>
 * 运行结果: 先打印one...,后打印two...
 * <p>
 * 解释:
 * 被static synchronized修饰相当于synchronized(Test8LockDemo.class) { xxx },静态同步方法锁定的是这个类Class对象,也即是整个类的模板都被锁住了。
 * 因为两个同步方法都被static修饰了,即便用了两个不同的对象调用方法,但是用的模板还是Test8LockDemo.class, 所以两个方法用的是同一个锁对象,所以先打印one...
 */
public class Test8LockDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareResource shareResource = new ShareResource();
        ShareResource shareResource2 = new ShareResource();
        new Thread(() -> shareResource.one()).start();
        //这里加一点延迟,为了保证前面的线程先获取执行权.
        Thread.sleep(1000);
        new Thread(() -> shareResource2.two()).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public static synchronized void one() {
        //one()方法睡眠5秒
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("one...");
    }

    public static synchronized void two() {
        System.out.println("two...");
    }

}

运行结果:

one...
two...

解释:

被static synchronized修饰相当于synchronized(Test8LockDemo.class) { xxx },静态同步方法锁定的是这个类Class对象,也即是整个类的模板都被锁住了。
因为两个同步方法都被static修饰了,即便用了两个不同的对象调用方法,但是用的模板还是Test8LockDemo.class, 所以两个方法用的是同一个锁对象,所以先打印one...
  • 第七种情况: 一个静态同步方法,一个普通同步方法,一个ShareResource对象,先打印one还是two
/**
 * 线程八锁第七种情况: 一个静态同步方法,一个普通同步方法,一个ShareResource对象,先打印one还是two
 * <p>
 * 运行结果: 先打印two...,5秒后打印one...
 * <p>
 * 解释:
 * one()方法被synchronized和static修饰,锁定的是ShareResource.class类对象,也即是整个类的模板;
 * two()方法被synchronized修饰,锁定的是shareResource这个方法调用者对象;
 * 由于两个方法锁的对象不是同一个,所以两个方法用的不是同一个锁,由于one()方法有5秒延迟,所以先打印two...
 */
public class Test8LockDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> shareResource.one()).start();
        //这里加一点延迟,为了保证前面的线程先获取执行权.
        Thread.sleep(1000);
        new Thread(() -> shareResource.two()).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public static synchronized void one() {
        //one()方法睡眠5秒
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("one...");
    }

    public synchronized void two() {
        System.out.println("two...");
    }

}

运行结果:

two...
one...

解释:

one()方法被synchronized和static修饰,锁定的是ShareResource.class类对象,也即是整个类的模板;
two()方法被synchronized修饰,锁定的是shareResource这个方法调用者对象;
由于两个方法锁的对象不是同一个,所以两个方法用的不是同一个锁,由于one()方法有5秒延迟,所以先打印two...
  • 第八种情况: 一个静态同步方法,一个普通同步方法,两个ShareResource对象,先打印one还是two
/**
 * 线程八锁第八种情况: 一个静态同步方法,一个普通同步方法,两个ShareResource对象,先打印one还是two
 * <p>
 * 运行结果: 先打印two...,5秒后打印one...
 * <p>
 * 解释:
 * one()方法被synchronized和static修饰,锁定的是ShareResource.class类对象,也即是整个类的模板;
 * two()方法仅仅被synchronized修饰,锁定的是shareResource2这个方法调用者对象;
 * 由于两个方法锁的对象不是同一个,所以两个方法用的不是同一个锁,由于one()方法有5秒延迟,所以先打印two...
 */
public class Test8LockDemo {
    public static void main(String[] args) throws InterruptedException {
        ShareResource shareResource = new ShareResource();
        ShareResource shareResource2 = new ShareResource();
        new Thread(() -> shareResource.one()).start();
        //这里加一点延迟,为了保证前面的线程先获取执行权.
        Thread.sleep(1000);
        new Thread(() -> shareResource2.two()).start();
    }
}

/**
 * 共享资源类
 */
class ShareResource {

    public static synchronized void one() {
        //one()方法睡眠5秒
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("one...");
    }

    public synchronized void two() {
        System.out.println("two...");
    }

}

运行结果:

two...
one...

解释:

one()方法被synchronized和static修饰,锁定的是ShareResource.class类对象,也即是整个类的模板;
two()方法仅仅被synchronized修饰,锁定的是shareResource2这个方法调用者对象;
由于两个方法锁的对象不是同一个,所以两个方法用的不是同一个锁,由于one()方法有5秒延迟,所以先打印two...

三、总结

  • 1. 一个类里面如果有多个synchronized方法,在使用同一个对象调用的前提下,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法;
  • 2. 普通方法后和同步锁无关,执行时无需判断是否拥有锁 ;
  • 3. 静态同步方法锁定的是整个类.class对象,普通同步方法锁定的是this,即方法的调用者 ;
  • 4. 判断8锁,主要关注是否锁定的同一个对象即可; 
发布了220 篇原创文章 · 获赞 93 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/104523513