Java 深入理解synchronized锁

synchronized的特性

    1、原子性:所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
    2、可见性:可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。
    3、有序性:有序性值程序执行的顺序按照代码先后执行。
    4、可重入性:当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。

synchronized的案例

    synchronized可以修饰静态方法、成员函数,同时还可以直接定义代码块,但是归根结底它上锁的资源只有两类:一个是对象,一个是类。

package com.example.juc;

import java.util.concurrent.TimeUnit;

/**
 * @author zjt
 * @date 2021-03-06
 */
public class SynchronizedDemo {

    public static void main(String[] args) throws InterruptedException {
        Print print = new Print();
        // 问题1 仅调用普通加锁方法A B 打印的顺序一定是先A后B吗?
        // new Thread(print::printSyncA).start();
        // new Thread(print::printSyncB).start();
        // 结果
        // 跟CPU的调度有关,不一定是先A后B,若计算机性能足够好,先打印A的概率大一些
        // --------------

        // 问题2
        // 调用睡眠方法 A 与普通加锁方法B 打印的顺序是什么?
        // new Thread(print::printSyncSleepA,"A").start();
        // Thread.sleep(200); // 加一个睡眠时间保证线程A先启动
        // new Thread(print::printSyncB,"B").start();
        // 结果 一定是 先A 后B
        // 原因:1、synchronized关键字修饰方法 被锁住的是当前对象 this 两个方法锁住的是同一个对象
        //      2、.sleep() 方法不释放当前锁,占着锁等待 所以打印的顺序一定是 先A 后 B
        // 一个资源类中,有多个 synchronized关键字修饰的方法,多线程调用时,一个方法抢占到CPU执行
        // ,其它synchronized关键字修饰的方法需要等待
        // --------------

        // 问题3
        // 调用睡眠加锁方法A 普通方法C 打印的顺序是什么?
//        new Thread(print::printSyncSleepA, "A").start();
//        Thread.sleep(200); // 加一个睡眠时间保证线程A先启动
//        new Thread(print::printC, "C").start();
        // 结果 先C 后A
        // C是普通方法,没有使用synchronized关键字修饰,方法C并不需要获取锁 跟方法A各不相干
        // --------------

        // 问题4 新建对象print1 分别 调用睡眠加锁方法A 普通加锁方法B
        // Print print1 = new Print();
        // new Thread(print::printSyncSleepA, "A").start();
        // Thread.sleep(200); // 加一个睡眠时间保证线程A先启动
        // new Thread(print1::printSyncB, "B").start();
        // 结果 先B后A
        // 原因:加锁的对象不再是同一个,它们之间也是各不相干
        // --------------

        // 问题5 调用静态加锁方法A 静态加锁方法B
        // new Thread(Print::printStaticSyncA, "A").start();
        // Thread.sleep(200); // 加一个睡眠时间保证线程A先启动
        // new Thread(Print::printStaticSyncB, "B").start();
        // 静态方法可以直接使用类名.方法调用,锁住的对象不再是 this 而是 Print.class
        //  无论在被new出多少个对象,都是同一个锁
        // --------------
        
        // 问题6 调用静态加锁方法A 普通加锁方法B
        new Thread(Print::printStaticSyncA, "A").start();
        Thread.sleep(200); // 加一个睡眠时间保证线程A先启动
        new Thread(print::printSyncB, "B").start();
        // 两块代码锁住的不是同一个对象,所以打印的顺序是 先B后A
    }
}

class Print {

    // 静态加锁方法打印A
    public static synchronized void printStaticSyncA() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("A");
    }

    // 静态加锁方法打印B
    public static synchronized void printStaticSyncB() {
        System.out.println("B");
    }

    // 普通加锁方法打印A
    public synchronized void printSyncA() {
        System.out.println("A");
    }

    // 普通加锁并且线程睡眠3秒方法打印A
    public synchronized void printSyncSleepA() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("A");
    }

    // 普通加锁方法打印B
    public synchronized void printSyncB() {
        System.out.println("B");
    }

    // 普通方法打印C
    public void printC() {
        System.out.println("C");
    }

    // 普通方法打印C
    public void printSyncC() {
        // 在同步代码块中显示声明锁的对象 在资源竞争时更加清晰一点
        synchronized (this) {
            System.out.println("C");
        }
    }

    // 普通方法打印C
    public void printSyncC1() {
        synchronized (Print.class) {
            System.out.println("C");
        }
    }
}

synchronized简单解释

    synchronized实现同步的基础是:Java中的每一个对象都可以作为锁。
    具体表现为3中方式:
    1、对于普通同步方法:锁的是当前实例对象。
    2、对于静态同步方法:锁的是当前类的Class对象。
    3、对于同步方法块:锁的是synchronized ()括号中显示声明的对象。
    当一个线程试图访问同步代码时,首先必须获得锁,执行完成或者抛出异常时必须释放锁。也就是说一个实例对象的非静态同步代码方法获取锁后,该实例对象其它非静态同步方法必须等待获取锁的方法释放锁后,才能获取锁。同理静态方法也是如此,不过锁的是当前类的Class对象。
    synchronized有两种形式上锁,一个是对方法上锁,一个是构造同步代码块。他们的底层实现其实都一样,在进入同步代码之前先获取锁,获取到锁之后锁的计数器+1,同步代码执行完锁的计数器-1,如果获取失败就阻塞式等待锁的释放。只是他们在同步块识别方式上有所不一样,从class字节码文件可以表现出来,是monitorenter和monitorexit指令操作。

 public printSyncC()V
    TRYCATCHBLOCK L0 L1 L2 null
    TRYCATCHBLOCK L2 L3 L2 null
   L4
    LINENUMBER 115 L4
    ALOAD 0
    DUP
    ASTORE 1
    MONITORENTER
   L0
    LINENUMBER 116 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "C"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 117 L5
    ALOAD 1
    MONITOREXIT
   L1
    GOTO L6
   L2
   FRAME FULL [com/example/juc/Print java/lang/Object] [java/lang/Throwable]
    ASTORE 2
    ALOAD 1
    MONITOREXIT
   L3
    ALOAD 2
    ATHROW
   L6
    LINENUMBER 118 L6
   FRAME CHOP 1
    RETURN
   L7
    LOCALVARIABLE this Lcom/example/juc/Print; L4 L7 0
    MAXSTACK = 2
    MAXLOCALS = 3

    我在自学时找到一篇介绍synchronized实现原理文章,写的非常不错,附上原文链接
    大神,快来碗里 的synchronized实现原理

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值