synchronized的8大应用场景分析

synchronized的8大应用场景分析

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

   1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; ---对象锁

  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; --对象锁

  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; ---类锁

  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。--类锁

场景一:两个线程同时访问同一个对象的同步方法。

这是经典的对象锁中的方法锁,两个线程争夺同一个对象锁,但是两个线程是属于同一对象的,所以会互相等待,是线程安全的。

package com.demo.thread;

public class SynchronizedTest implements Runnable{
   
    static SynchronizedTest instance1 = new SynchronizedTest();

    @Override
    public void run() {
        method();
    }

    // 用synchronized对method方法机进行同步
    private  synchronized void method(){
        System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance1);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()){
        }
        System.out.println("测试结束");
    }
}

运行结果:
线程名Thread-0,开始运行
线程Thread-0,运行结束
线程名Thread-1,开始运行
线程Thread-1,运行结束
测试结束

场景二:两个线程同时访问两个对象的锁。

这个就是对象锁失效的场景,原因在访问的是两个对象的同步方法,那么这两个线程分别持有的是两个线程的锁,因此两线程之间是相互不影响的。这时加锁启不到任何作用。我们加锁的目的就是为了让多个线程竞争同一把锁。因此这种场景下是线程不安全的。

package com.demo.thread;

public class SynchronizedTest implements Runnable{
    //创建两个不同的对象
    static SynchronizedTest instance1 = new SynchronizedTest();
    static SynchronizedTest instance2 = new SynchronizedTest();
    
    @Override
    public void run() {
        method();
    }

    // 用synchronized对method方法机进行同步
    private synchronized void method(){
        System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()){
        }
        System.out.println("测试结束");
    }
}
运行结果:
线程名Thread-0,开始运行
线程名Thread-1,开始运行
线程Thread-0,运行结束
线程Thread-1,运行结束
测试结束

场景三:两个线程同时访问(一个或两个)对象的静态同步方法

这个场景即是场景二中的线程安全的解决方法,在方法上添加了static方法,作用对象是这个类的所有对象,所以是线程安全的。

package com.demo.thread;

/**
 * @Author Zhangnana
 * @DATE 2020/12/19 9:39
 * @Version 1.0
 */
public class SynchronizedTest implements Runnable{
    //创建两个不同的对象
    static SynchronizedTest instance1 = new SynchronizedTest();
    static SynchronizedTest instance2 = new SynchronizedTest();

    @Override
    public void run() {
        method();
    }

    // 用synchronized对method方法机进行同步
    private synchronized static void method(){
        System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()){
        }
        System.out.println("测试结束");
    }
}
结果:
 	线程名Thread-0,开始运行
	线程Thread-0,运行结束
	线程名Thread-1,开始运行
	线程Thread-1,运行结束
	测试结束

场景四:两个线程分别同时访问一个或两个对象的同步方法和非同步方法

这个场景是指两个线程其中一个访问的是同步方法,另一个访问的是非同步方法,此时,线程是否安全呢?

我们可以很确定的说是线程不安全的。从结果可以看出是线程不安全的。

package com.demo.thread;

/**
 * @Author Zhangnana
 * @DATE 2020/12/19 9:39
 * @Version 1.0
 */
public class SynchronizedTest implements Runnable{
    //创建两个不同的对象
    static SynchronizedTest instance1 = new SynchronizedTest();
    static SynchronizedTest instance2 = new SynchronizedTest();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method0();
        }
        if (Thread.currentThread().getName().equals("Thread-1")){
            method1();
        }
    }

    // 同步方法
    private synchronized static void method0(){
        System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
    }

    //普通方法
    public void method1(){
        System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()){
        }
        System.out.println("测试结束");
    }
}

结果
	线程名Thread-0,开始运行
	线程名Thread-1,开始运行
	线程Thread-1,运行结束
	线程Thread-0,运行结束
	测试结束

场景五:两个对象访问同一个对象中的同步方法,且同步方法中调用非同步方法

这个场景是用两个线程去调用同步方法,同步方法中去调用非同步方法,在用一个线程直接去调用普通方法。从结果中可以看出,thread0和thread2的普通方法是并行运行的,此时是线程不安全的。若想做到线程安全,必须确保在同步方法中调用非同步方法时,没有别的线程对其进行操作。

package com.demo.thread;

/**
 * @Author Zhangnana
 * @DATE 2020/12/19 9:39
 * @Version 1.0
 */
public class SynchronizedTest implements Runnable{
    //创建两个不同的对象
    static SynchronizedTest instance1 = new SynchronizedTest();
    static SynchronizedTest instance2 = new SynchronizedTest();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method0();
        }

    }

    // 同步方法
    private synchronized  void method0(){
        System.out.println("线程名" + Thread.currentThread().getName()+"同步方法,开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "同步方法,运行结束。开始调用普通方法");
        method1();
    }

    //普通方法
    public void method1(){
        System.out.println("线程名" + Thread.currentThread().getName()+"普通方法,开始运行");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "普通方法,运行结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance1);
        Thread thread3 = new Thread(instance1);
        thread1.start();
        thread2.start();
        thread3.start();
        while (thread1.isAlive() || thread2.isAlive() || thread3.isAlive()){
        }
        System.out.println("测试结束");
    }
}

线程名Thread-2同步方法,开始运行
线程名Thread-0普通方法,开始运行
线程Thread-2同步方法,运行结束。开始调用普通方法
线程名Thread-2普通方法,开始运行
线程Thread-0普通方法,运行结束
线程Thread-2普通方法,运行结束
线程名Thread-1同步方法,开始运行
线程Thread-1同步方法,运行结束。开始调用普通方法
线程名Thread-1普通方法,开始运行
线程Thread-1普通方法,运行结束
测试结束

场景六:两个线程同时访问同一个对象的不同的同步方法

此时获得的是对象锁,同一对象中,访问不同的同步方法是线程安全的。若是访问的是不同对象中的不同的同步方法时线程不安全的。根据synchronized修饰的具体内容,判断获取的是哪种锁。

package com.demo.thread;

/**
 * @Author Zhangnana
 * @DATE 2020/12/19 9:39
 * @Version 1.0
 */
public class SynchronizedTest implements Runnable{
    //创建两个不同的对象
    static SynchronizedTest instance1 = new SynchronizedTest();
    static SynchronizedTest instance2 = new SynchronizedTest();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }
        if (Thread.currentThread().getName().equals("Thread-1")){
            method0();
        }

    }

    // 同步方法
    private synchronized  void method0(){
        System.out.println("线程名" + Thread.currentThread().getName()+"同步方法,开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "同步方法,运行结束。开始调用普通方法");
        method1();
    }

    //普通方法
    public synchronized void method1(){
        System.out.println("线程名" + Thread.currentThread().getName()+"普通方法,开始运行");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "普通方法,运行结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance1);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()){
        }
        System.out.println("测试结束");
    }
}

线程名Thread-0普通方法,开始运行
线程Thread-0普通方法,运行结束
线程名Thread-1同步方法,开始运行
线程Thread-1同步方法,运行结束。开始调用普通方法
线程名Thread-1普通方法,开始运行
线程Thread-1普通方法,运行结束
测试结束

场景七:两个线程分别同时访问静态synchronized和非静态synchronized

方法

这个场景的本质是两个线程获取的是不是同一把锁的问题,我们知道静态synchronized方法获取的类锁,非静态获取得是对象锁,两个线程拿到的锁是不同的锁,所有不会互相等待,是线程不安全的。

package com.demo.thread;

/**
 * @Author Zhangnana
 * @DATE 2020/12/19 9:39
 * @Version 1.0
 */
public class SynchronizedTest implements Runnable{
    //创建两个不同的对象
    static SynchronizedTest instance1 = new SynchronizedTest();
    static SynchronizedTest instance2 = new SynchronizedTest();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }
        if (Thread.currentThread().getName().equals("Thread-1")){
            method0();
        }

    }

    // 同步方法
    private synchronized static void method0(){
        System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "运行结束。");
    }

    //普通方法
    public synchronized void method1(){
        System.out.println("线程名" + Thread.currentThread().getName()+",开始运行");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + ",运行结束");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()){
        }
        System.out.println("测试结束");
    }
}

线程名Thread-0,开始运行
线程名Thread-1,开始运行
线程Thread-1运行结束。
线程Thread-0,运行结束
测试结束

场景八:同步方法抛出异常后,JVM会自定释放锁的情况

只有当同步方法执行完成或执行时抛出异常这两种情况,才会释放锁。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值