写点什么

Java- 技术专题 -Synchronized 锁的分析

发布于: 2021 年 04 月 01 日
Java-技术专题-Synchronized锁的分析

概念


synchronized Java 中的关键字,是利用 Mutx 互斥锁的机制来实现同步的,底层采用 Futex 锁进行控制,实现原理为 fast mutex 互斥锁,因为他可以动态模糊用户空间以及内核空间的转换,所以非常方便,命名为 fast 快速。


锁机制有如下两种特性:

  • 互斥性同一时间只允许一个线程持有某个对象锁通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性

  • 可见性:必须确保在锁被释放之前,对共享变量所做的修改对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值)否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。

对象锁和类锁

对象锁

在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。

类锁

在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。

synchronized 的用法分类

根据修饰对象分类

synchronized 可以修饰方法和代码块

  • 修饰代码块

    synchronized(this|object) {}

    synchronized(类.class) {}

  • 修饰方法

    修饰非静态方法

    修饰静态方法

根据获取的锁分类

  • 获取对象锁

    synchronized(this|object) {}

    修饰非静态方法

  • 获取类锁

    synchronized(类.class) {}

    修饰静态方法,非静态方法

synchronized 的用法详解

这里根据获取的锁分类来分析 synchronized 的用法

对象锁

这个对象是新建的,跟其他对象没有任何关系:

/** * synchronized 修饰非静态方法 */    private void sync5() {        Log.d(TAG,Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));        synchronized (new SyncThread()) {            try {                Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));                Thread.sleep(2000);                Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }
复制代码

文章后面所有例子都是采用这四个线程,只是对方法进行修改:

SyncThread syncThread = new SyncThread();        Thread F_thread1 = new Thread(new SyncThread(), "F_thread1");        Thread F_thread2 = new Thread(new SyncThread(), "F_thread2");        Thread F_thread3 = new Thread(syncThread, "F_thread3");        Thread F_thread4 = new Thread(syncThread, "F_thread4");        F_thread1.start();        F_thread2.start();        F_thread3.start();        F_thread4.start();
复制代码

 运行结果如下:

四个线程同时开始,同时结束,因为作为锁的对象与线程是属于不同的实例。

类锁

/**     * synchronized 修饰非静态方法     */    private void sync5() {        Log.d(TAG,Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));        synchronized (MainActivity.class) {            try {                Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));                Thread.sleep(2000);                Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }
复制代码

 运行结果如下:


可以发现,采用类锁一次只能通过一个。即使采用的是 MainActivity.class 这个类锁。

this 对象锁

/**     * synchronized 修饰非静态方法     */    private void sync5() {        Log.d(TAG,Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));        synchronized (this) {            try {                Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));                Thread.sleep(2000);                Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }
复制代码

  运行结果如下:


可以发现线程 1,2 同时结束,3,4 有先后,原因是 3,4 同属于一个实例。

synchronized 修饰方法

作用范围是整个方法,所以方法中所有的代码都是同步的:


 /**     * synchronized 修饰非静态方法     */    private synchronized void sync5() {        Log.d(TAG, Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));        try {            Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));            Thread.sleep(2000);            Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));        } catch (InterruptedException e) {            e.printStackTrace();        }    }
复制代码


 修饰非静态方法:



对于非静态方法,同一个实例的线程访问会被拦截,非同一实例可以同时访问。 即此时是默认对象锁(this)。

修饰静态方法结果



可以看出来,静态方法默认类锁。

总结

  1. 对于静态方法,由于此时对象还未生成,所以只能采用类锁;

  2. 只要采用类锁,就会拦截所有线程,只能让一个线程访问。

  3. 对于对象锁(this),如果是同一个实例,就会按顺序访问,但是如果是不同实例,就可以同时访问。

  4. 如果对象锁跟访问的对象没有关系,那么就会都同时访问。


用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论

发布
暂无评论
Java-技术专题-Synchronized锁的分析