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 = 10
add 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 = 20
a 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 print1
do work print2
do 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同步之前的代码逻辑,但是使用关键字同步的部分是互斥的,先到达的线程占有对象锁,后面的线程会被阻塞,直到对象锁被前面的线程释放。

四、总结

synchronized是一种悲观锁:

1、多线程竞争的情况下,频繁的加锁解锁导致过多的线程上下文切换,由于java线程是基于操作系统内核线程实现的,所以如果阻塞或者唤醒线程都需要切换到内核态操作,这会极大的浪费CPU资源。

2、一个线程持有锁,会导致后面其他请求该锁的线程挂起。

synchronized锁优化:自旋锁

很多应用中共享数据的锁定,只会持续很短的时间,如果因为这很短的时间做线程的挂起和恢复,会造成很大的性能消耗(因为java线程对应操作系统的内核线程),让当前线程不停地的在循环体内执行不被挂起,这就是自旋锁的实现,后面会展开详细介绍。

时间: 2024-08-04 06:00:48

Synchronized及其实现原理的相关文章

HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别

HashMap 是否是线程安全的,如何在线程安全的前提下使用 HashMap,其实也就是HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别.当时有些紧张只是简单说了下HashMap不是线程安全的:Hashtable 线程安全,但效率低,因为是 Hashtable 是使用 synchronized 的,所有线程竞争同一把锁:而 ConcurrentHashMap 不仅线程安全而且效率高,因为它包含一个 segment 数组,将

Java并发编程:Synchronized及其实现原理

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 接下

Java Synchronized及实现原理

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 首先来看一下没有使用同步的情况 public class SynchronizedTest { public void method1(){ System.out

【转】Java并发编程:Synchronized及其实现原理

一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,

面试必备:synchronized的底层原理?

最近更新的XX必备系列适合直接背答案,不深究,不喜勿喷. 你能说简单说一下synchronize吗? 可别真简单一句话就说完了呀~ 参考回答: synchronize是java中的关键字,可以用来修饰实例方法.静态方法.还有代码块:主要有三种作用:可以确保原子性.可见性.有序性,原子性就是能够保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等该线程处理完数据后才能进行:可见性就是当一个线程在修改共享数据时,其他线程能够看到,保证可见性,volatile关键字也有这个功能:有序性就是,被s

Java并发编程 Synchronized及其实现原理

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题. Java中每一个对象都可以作为锁,这是synchronized实现同步的基础: 1.普通同步方法,锁是当前实例对象 public class SynchronizedTest { 4 public synchronized void method1(){ 5 System

synchronized的实现原理-java并发编程的艺术读书笔记

1.synchronized实现同步的基础 Java中的每个对象都是可以作为锁,具体有3种表现. 1.对于普通同步方法,锁是当前实例对象. 2.对于静态同步方法,锁是当前类的Class对象. 3.对于同步方法块,锁是Synchonized括号里面的配置对象. 当前一个线程试图访问同步代码块时,它首先必须得到锁,退出或者抛出异常时候必须释放锁.那么锁到底存在什么地方? 从JVM规范可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码快同步,

java线程总结--synchronized关键字,原理以及相关的锁

在多线程编程中,synchronized关键字非常常见,当我们需要进行"同步"操作时,我们很多时候需要该该关键字对代码块或者方法进行锁定.被synchronized锁定的代码块,只能同时有一条线程访问该代码块. 上面是很多人的认识,当然也是我之前对synchronized关键字的浅显认识,其实上面的观点存在一定的偏差.在参考了很多文章以及自己动手测试过相关代码后,我觉得有必要记录下自己对synchronized关键字的一些理解,在这个过程,会简单说说synchronized关键字的具体

深入分析synchronized的实现原理

基础概念 synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时可以保证共享变量对内存可见性. Java中每一个对象都可以作为锁,这是synchronized实现同步的基础: 普通同步方法,锁是当前实例对象 静态同步方法,锁是当前类的class对象 同步方法块,锁是括号里面的对象 当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁. 底层实现原理 如何来实现这个机制呢?我们先看如下一段简单代码: publi