Java Synchronized及实现原理

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。从语法上讲,Synchronized总共有三种用法:

  (1)修饰普通方法

  (2)修饰静态方法

  (3)修饰代码块

首先来看一下没有使用同步的情况

public class SynchronizedTest {

    public void method1(){

        System.out.println("method1 start");
        try{

            System.out.println("method1 excute");
            Thread.sleep(3000);
        }catch (Exception e){

            e.printStackTrace();
        }
        System.out.println("method1 end");

    }

    public void method2(){

        System.out.println("method2 start");
        try{

            System.out.println("method2 excute");
            Thread.sleep(1000);
        }catch (Exception e){

            e.printStackTrace();
        }
        System.out.println("method2 end");

    }

    public static void main(String[] args){

        final SynchronizedTest test=new SynchronizedTest();
        new Thread(new Runnable() {
            @Override
            public void run() {

                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {

                test.method2();
            }
        }).start();
    }
}

输出结果为:(线程1和线程2同时进入执行状态,线程2执行速度比线程1快,所以线程2先执行完成,这个过程中线程1和线程2是同时执行的。)

method1 start
method1 excute
method2 start
method2 excute
method2 end
method1 end

使用synchronized修饰普通方法,其他保持不变,输出结果为

method1 start
method1 excute
method1 end
method2 start
method2 excute
method2 end

可以很明显的看出,线程2需要等待线程1的method1执行完成才能开始执行method2方法。为什么? 因为使用synchronized关键字修饰方法,则锁为this,即调用method的类的实例test。

如果在main方法中增加一个

final SynchronizedTest test1=new SynchronizedTest();并且使用test1调用method2,则输出结果如下:

method1 start
method1 excute
method2 start
method2 excute
method2 end
method1 end

为什么? 因为虽然使用了关键字synchronized,但是调用synchronized的对象不一致,也就是synchronized锁的不是同一个对象,所以同步并未生效。

使用synchronize修饰static方法(即将方法修改为static synchronized,其他不变),输出结果为

method1 start
method1 excute
method1 end
method2 start
method2 excute
method2 end

可以看出为顺序执行

如果在main方法中增加一个

final SynchronizedTest test1=new SynchronizedTest();并且使用test1调用method2,则输出结果也是一致的,为什么呢?

对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。

使用synchronized修饰代码块

public class SynchronizedTest {

    public void method1(){

        System.out.println("method1 start");
        try{

            synchronized(this){

                System.out.println("method1 excute");
                Thread.sleep(3000);
            }
        }catch (Exception e){

            e.printStackTrace();
        }
        System.out.println("method1 end");

    }

    public void method2(){

        System.out.println("method2 start");
        try{

            synchronized(this){

                System.out.println("method2 excute");
                Thread.sleep(1000);
            }

        }catch (Exception e){

            e.printStackTrace();
        }
        System.out.println("method2 end");

    }

    public static void main(String[] args){

        final SynchronizedTest test=new SynchronizedTest();
//        final SynchronizedTest test1=new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {

                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {

                test.method2();
            }
        }).start();
    }
}

输出结果为:

method1 start
method1 excute
method2 start
method1 end
method2 excute
method2 end

可以看出结果为非顺序执行,为什么?

这就涉及到synchronized关键字的原理了

先看synchronized代码块,首先对代码进行javac编程,再进行javap -c 反编译得出结果如下:

关于这两条指令的作用,我们直接参考JVM规范中描述:

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
? If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
? If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
? If another thread already owns the monitor associated with objectref, the thread blocks until the monitor‘s entry count is zero, then tries again to gain ownership.

这段话的大概意思为:

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit: 

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

这段话的大概意思为:

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

  通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

再看下synchronized同步方法:

synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能

执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行

状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有

一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)

。  
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成

员变量的访问。

synchronized方法实际上等同于用一个synchronized块包住方法中的所有语句,然后在synchronized块的括号中传入this关键字。当然如果是静态方法,需要锁定的则是class对象。

可能一个方法中只有几行代码涉及到线程同步的问题,所以synchronized块比synchronized方法更近细粒度的控制了多个线程的访问,只有synchronized块中的内容不能同时被多个线程访问,方法中的其他语句仍然可以同时被多个线程所访问(包括synchronized块之前和之后的)。

运行结果解释

  有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。

1、同步普通方法结果:

  虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。

2、同步静态方法结果:

  虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。

3、代码块同步结果:

  对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

时间: 2024-10-28 20:32:12

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

Java synchronized的实现原理与应用

在多线程中synchronized应该是我们运用的最多的,很多人会称呼它为重量级锁.java中的每一个对象都可以作为锁.具体表现为以下三种形式. 对于普通同步方法,锁是当前实例对象. //图书 class Books { private int id;// 图书ID private String name; // 图书名称 private int number; // 图书数量 //存书 public void stockBooks(int count) { number+=count; try

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

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

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

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

Java Synchronized 锁的实现原理详解及偏向锁-轻量锁-重量锁

Synchronize是重量级锁吗?是互斥锁吗? 它的实现原理? 前言 线程安全是并发编程中的重要关注点,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多个线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只

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

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

Java 线程池的原理与实现

最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知识空白的同时,也学会了不少新的知识(比如 NIO),或者称为新技术吧.线程池就是其中之一,一提到线程,我们会想到以前<操作系统>的生产者与消费者,信号量,同步控制等等.一提到池,我们会想到数据库连接池,但是线程池又如何呢? 建议:在阅读本文前,先理一理同步的知识,特别是syncronized同步关键字的用

java数据库连接池实现原理

一.为什么在连接数据库时要使用连接池 数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出.  一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下. 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个"池"里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请.使用和释放.对于多于连接池中连接数的并发请求,应该在请求队列

线程池;java实现线程池原理

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙.如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线程可以排队,但他们要等到其他线程完成后才启动. 组成部分 1.线程池

Java线程池的原理及几类线程池的介绍

刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所花的时间以及系统资源的开销 如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及"过度切换". 线程池工作原理: 为什么要用线程池? 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到