Synchronized及其实现原理

简介:

并发编程中synchronized一直是元老级角色,我们称之为重量级锁。主要用在三个地方:

1、修饰普通方法,锁是当前实例对象。

2、修饰类方法,锁是当前类的Class对象。

3、修饰代码块,锁是synchronized括号里面的对象。

一、synchronized实现原理

当一个线程试图访问同步代码块时,必须得到锁。在退出或抛出异常时必须释放锁,JVM是基于进入和退出Monitor来实现方法同步和代码块同步。

我们来看下synchronized的字节码:

public class SynchronizedTest
{    public void addNum1(String userName)
    {
    }    
    public void addNum2(String userName)
    {        synchronized(this)
        {
        }
    }    
    public synchronized void addNum3(String userName)
    {
    }
}

在字节码里可以看到,用synchronizde修饰的同步代码块多了两个指令:monitorenter、monitorexit;

代码块同步是使用monitorenter、monitorexit指令实现的,而方法同步是使用另外一种方式实现的,但是方法同步也可以使用这两个指令来实现。

monitorenter指令是编译后插入到同步代码块的开始位置,而monitorexit是插入到方法的结束和异常位置。任何一个对象都有一个monitor与之关联。线程执行到monitorenter指令处时,会尝试获取对象所对应的monitor所有权,即尝试获得对象的锁。

二、修饰普通方法 锁是当前实例对象

我们先来看下将实例对象作为锁的概念:

public class AddNumTest
{    private int num = 0;    
    public synchronized void addNum(String str)
    {        try
        {            if ("a".equals(str))
            {
                num = 10;
                System.out.println("add a");
                Thread.sleep(2000);
            }            else
            {
                num = 20;
                System.out.println("add b");
            }
            System.out.println(str + " num = " + num);
        }        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

public class AddNumThreadOne implements Runnable
{    private AddNumTest at;    
    public AddNumThreadOne(AddNumTest at)
    {        this.at = at;
    }

    @Override    public void run()
    {
        at.addNum("a");
    }
}

public class AddNumThreadTwo implements Runnable
{    private AddNumTest at;    public AddNumThreadTwo(AddNumTest at)
    {        this.at = at;
    }
    
    @Override    public void run()
    {
        at.addNum("b");
    }
}

public class AddNum
{    public static void main(String[] args)
    {        //注意,这里传入同一个实例对象
        AddNumTest at = new AddNumTest();        //AddNumTest bt = new AddNumTest();
        Thread t1 = new Thread(new AddNumThreadOne(at));
        Thread t2 = new Thread(new AddNumThreadTwo(at));
        t1.start();
        t2.start();
    }
}

执行结果:

add a
a num = 10add b
b num = 20

前面解释过关键字synchronized的实现原理是使用对象的monitor来实现的,取的锁都是对象锁,而不是把一段代码或者函数作为锁。在并发情况下,如果并发情况下多线程竞争的是同一个对象,那么先来的获取该对象锁,后面的线程只能排队,等前面的线程执行完毕释放锁。

上面介绍的是同一个对象锁,我们来观察下获取不同的对象锁会是什么情况:

public static void main(String[] args)
    {        //注意,这里传入的不同的实例对象
        AddNumTest at = new AddNumTest();
        AddNumTest bt = new AddNumTest();
        Thread t1 = new Thread(new AddNumThreadOne(at));
        Thread t2 = new Thread(new AddNumThreadTwo(bt));
        t1.start();
        t2.start();
    }

执行结果:

add a
add b
b num = 20a num = 10

这里线程1、2抢占的是不同的锁,尽管线程1先到达同步代码块的位置,但是由于monitor不一样,所以不能阻塞线程2的执行。

三、synchronized锁重入

锁重入:当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到该对象的锁。但是这里有维护一个计数器,同一个线程每次得到对象锁计数器都会加1,释放的时候减1,直到计数器的数值为0的时候,才能被其他线程所抢占

public class AgainLock
{    public synchronized void print1()
    {
        System.out.println("do work print1");
        print2();
    }    
    public synchronized void print2()
    {
        System.out.println("do work print2");
        print3();
    }    
    public synchronized void print3()
    {
        System.out.println("do work print3");
    }
}

public class AgainLockTest
{    public static void main(String[] args)
    {
        Thread t = new Thread(new Runnable()
        {
            @Override            public void run()
            {
                AgainLock al = new AgainLock();
                al.print1();
            }
        });
        t.start();
    }
}

执行结果:

do work print1do work print2do work print3

这里面三个同步方法,使用的锁都是该实例对象的同步锁,同一个线程在执行的时候,每次都是在锁没有释放的时候,就要重新再去获取同一把对象锁。从运行结果可以看出,关键字synchronized支持同一线程锁重入。

四、synchronized同步代码块

用synchronized同步方法的粒度过大,有时候一个方法里面的业务逻辑很多,但是我们想对同步的部分进行单独定制,这时候就可以使用synchronized来同步代码块。

public class SynchronizedTest1
{    public void doWorkTask()
    {        for(int i=0;i<100;i++)
        {
            System.out.println("nosynchronized thread name =" + Thread.currentThread().getName()                + ";i=" + i);
        }        synchronized(this)
        {            for(int i=0;i<100;i++)
            {
                System.out.println("thread name =" + Thread.currentThread().getName()                    + ";i=" + i);
            }
        }
    }
}

如果在并发情况下,调用这个类的同一个实例,线程A和B可以同时执行使用synchronized同步之前的代码逻辑,但是使用关键字同步的部分是互斥的,先到达的线程占有对象锁,后面的线程会被阻塞,直到对象锁被前面的线程释放。














本文转自xsster51CTO博客,原文链接: http://blog.51cto.com/12945177/1948502,如需转载请自行联系原作者




相关文章
|
6天前
|
云安全 人工智能 安全
AI被攻击怎么办?
阿里云提供 AI 全栈安全能力,其中对网络攻击的主动识别、智能阻断与快速响应构成其核心防线,依托原生安全防护为客户筑牢免疫屏障。
|
16天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
10天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
625 217
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
863 61
|
8天前
|
人工智能 移动开发 自然语言处理
2025最新HTML静态网页制作工具推荐:10款免费在线生成器小白也能5分钟上手
晓猛团队精选2025年10款真正免费、无需编程的在线HTML建站工具,涵盖AI生成、拖拽编辑、设计稿转代码等多种类型,均支持浏览器直接使用、快速出图与文件导出,特别适合零基础用户快速搭建个人网站、落地页或企业官网。
1358 157
|
5天前
|
编解码 Linux 数据安全/隐私保护
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
258 138
|
7天前
|
存储 安全 固态存储
四款WIN PE工具,都可以实现U盘安装教程
Windows PE是基于NT内核的轻量系统,用于系统安装、分区管理及故障修复。本文推荐多款PE制作工具,支持U盘启动,兼容UEFI/Legacy模式,具备备份还原、驱动识别等功能,操作简便,适合新旧电脑维护使用。
553 109