重新认识synchronized(上)

synchronized在JDK5之前一直被称为重量级锁,是一个较为鸡肋的设计,而在JDK6对synchronized内在机制进行了大量显著的优化,加入了CAS,轻量级锁和偏向锁的功能,性能上已经跟ReentrantLock相差无几,而且synchronized在使用上更加简单,不易出错(避免哲学家就餐问题造成的死锁),因此如果仅仅是为了实现互斥,而不需要使用基于Lock的附加属性(中断、条件等),推荐优先使用synchronized。

synchronized用法

synchronized有同步方法和同步语句块两种形式,分锁定对象和作用域两个维度,当锁定的对象进入作用域时,互斥才会生效。一个线程访问拥有相同锁定对象时,进入作用域时必须以串行方式进行。修饰的对象有:修饰普通方法(又称为同步方法),修饰静态方法,修饰对象实例,修饰class literals(类名称字面常量)。

修饰普通方法

又称同步方法,锁定的是调用这个同步方法对象。一个线程访问实例对象中的synchronized代码块时,其他试图访问该对象synchronized修饰的区域的线程将阻塞。同一个对象共享一把锁,同一时间只能有一个synchronized区域可以获得锁,可以称为对象锁。需要注意两点:(1)作用只会在同一个实例上,(2)对象中的所有synchronized修饰的区域。例如:对于fooA对象,线程t1,t2访问methodA是互斥的,线程t1,t2分别访问methodA,methodB同样是互斥的;当访问methodA时,methodB 不可以访问,methodC可以访问。

 1 public class Foo implements Runnable {
 2     public static synchronized void methodA() {
 3         //TODO
 4     }
 5
 6     public static synchronized void methodB() {
 7         //TODO
 8     }
 9
10     public void methodC() {
11         //TODO
12     }
13
14     public static void main(String[] args) {
15         Foo fooA = new Foo();
16         Foo fooB = new Foo();
17
18         Thread t1 = new Thread(fooA, "t1");
19         Thread t2 = new Thread(fooB, "t2");
20         t1.start();
21         t2.start();
22     }
23 }

修饰静态方法

锁定是静态方法所属的类,此时该类共用一把锁,可以称为类锁。一个线程访问synchronized静态方法时,其他试图访问该类synchronized修饰的区域的线程都将阻塞,而非synchronized修饰的区域不会阻塞。修饰静态方法是修饰普通方法的变种版,前者锁定该类(跟对象没关系),后者指锁定当前对象。例如:线程t1,t2分别访问methodA,methodB同样是互斥的。

同步语句块

修饰对象实例的情况与修饰普通方法一致,区别在于只有线程进入同步语句块时,才是互斥的。而修饰class literals(类名称字面常量)与修饰静态方法一致。例如:methodD锁定的是当前对象,而methodE中的synchronized锁定的是Foo类的所有对象。

 1 public class Foo {
 2     public void methodD() {
 3         synchronized (this) {
 4             //TODO
 5         }
 6     }
 7
 8     public void methodE() {
 9         synchronized (Foo.class) {
10             //TODO
11         }
12     }
13 }

synchronized机制

synchronized是Java为线程间通信提供的一种方式,synchronized的这种同步机制是互斥锁机制。实现互斥的方式有:临界区(Critical Section),互斥量(Mutex),信号量(Semaphores)和事件(Event)。

synchronized是采用临界区的方式实现互斥的,通过对多线程的串行化来访问公共资源或一段代码,保证同一时刻只有一个线程能访问临界区内共享数据。如果多个线程试图同时访问临界区,其中一个线程抢占临界区后,其他试图访问该临界区的线程将会挂起,直到进入临界区的线程。举个栗子,会议室资源相对紧张,有多个团队都需要使用会议室,这时就看哪个团队的迅速了。当这个团队抢占到会议室后,就会挂起“正在会议中”的提示牌,其他需要使用会议室的人看到会等待,直到当前使用会议室的团队使用完,移走“正在会议中”的提示牌。

synchronized同时保证了可见性。互斥锁释放后,确保一个线程修改的对象状态,对其他线程是可见的,即在随后获取锁的线程中保证获得最新共享资源的状态。synchronized互斥锁是通过监视器(Monitor)实现的。

什么是监视器(Monitor)

监视器是一种同步机制。 通过获取和释放锁,保证线程间可以互斥地操作共享资源,同一时间最多只有一个线程占用监视器,即互斥(mutex);采用条件变量(Condition Variables),允许线程阻塞和等待另一个线程发送信号的方法弥补互斥锁的不足,这就是监视器的互斥和协作。

当多线程同时访问一段同步代码时,新请求的线程会首先加入到Entry Set集合中,通过竞争(compete)方式,同一时间只有一个线程可以竞争成功并获取监视器,进入The Owner。获取监视器的线程调用wait()后就会释放监视器,并进入Wait Set集合中等待满足条件时被唤醒。无论是acquire还是release都是使用原子指令如test-and-set、compare-and-set来实现自旋锁来保证对关键段的访问。
java-monitor

参考

    1. Java synchronized关键字解析
    2. 临界区,互斥量,信号量,事件的区别
    3. Monitor (synchronization))
    4. synchronized 与 object’s Monitor
时间: 2024-10-28 14:37:54

重新认识synchronized(上)的相关文章

synchronized的功能拓展:重入锁(读书笔记)

重入锁可以完全代替synchronized关键字.在JDK5.0的早期版本中,重入锁的性能远远好于synchronized,但是从JDK6.0开始.JDK在synchronized上做了大量的优化.使得两者的性能差距不大, public class ReenterLock implements Runnable { public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; /** * When

并发和多线程-八面玲珑的synchronized

上篇<并发和多线程-说说面试常考平时少用的volatile>主要介绍的是volatile的可见性.原子性等特性,同时也通过一些实例简单与synchronized做了对比. 相比较volatile,其实我们应该更加熟悉synchronized,平时开发中接触和使用也更多一些. 那么为什么说synchronized是八面玲珑呢,因为它可以混迹在很多"场所"(方法.代码块),与各种角色(类.对象)打交道. 也正是因为它的八面玲珑,所以就显得比较神秘,也比较复杂,今天就来追踪下sy

JDK并发工具之多线程团队协作:同步控制

一.synchronized的功能扩展:重入锁(java.util.concurrent.locks.ReentrantLock) 重入锁可以完全替代synchronized关键字.在JDK 5.0的早期版本中,重入锁的性能远远好于synchronized,但从JDK 6.0开始,JDK在syn-chronized上做了大量的优化,使得两者的性能差距并不大. 01 public class ReenterLock implements Runnable{ 02 public static Ree

队列同步器AbstractQueueSynchronizer

同步器是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作. 同步器主要使用的方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态.子类推荐被定义为自定义同步组件的静态内部类,同步器本身没有实现任何同步接口,仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用. 同步器的设计是基于模版方法模式的,即使用者需要继承同步器并重写指定的方法.同步器支持独占式获取同步状态和共享式获取同步状态 Java.co

Java面试准备之多线程

什么叫线程安全?举例说明 多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 比如无状态对象一定是线程安全的. 进程和线程的区别 调度: 线程是调度的基本单位,进程是拥有资源的基本单位.同一进程的中线程的切换不会引起进程的切换,不同进程中进行线程切换会引起进程的切换. 拥有资源:进程是拥有资源的基本单位,线程除了自身的栈外一般不拥有资源.而是和其他线程共享同一进程中

Java中String、StringBuffer、StringBuilder的比较与源 代码分析

Java中String.StringBuffer.StringBuilder的比较与源代码分析 众所周知String.StringBuffer.StringBuilder是java中常用的字符串类,下面我将从三个方面对他们三兄弟进行对比. 一.三者的数据组织及其功能实现 大家爱把String.StringBuffer.StringBuilder叫做三兄弟,经过分析代码发现说他俩三兄弟有点不太贴切,从组织结构上说,StringBuffer.StringBuilder更像是亲兄弟,这哥俩儿都有一个妈

队列同步器

队列同步器AbstractQueueSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础. 同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义.可以这样理解二者之间的关系:锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细

从头认识java-18.6 synchronized在其他对象上同步和ThreadLocal来消除共享对象的同步问题

这一章节我们来介绍在其他对象上同步与ThreadLocal. 前一章节我们使用了 1.synchronized在其他对象上同步 class ThreadA implements Runnable { private Object object = new Object(); private synchronized void test() throws InterruptedException { System.out.println("dosomething"); Thread.sl

jgs--多线程和synchronized

多线程 多线程是我们开发人员经常提到的一个名词.为什么会有多线程的概念呢?我们的电脑有可能会有多个cpu(或者CPU有多个内核)这就产生了多个线程.对于单个CPU来说,由于CPU运算很快,我们在电脑上运行多个软件时,每个软件在CPU上运行很短的时间就会切换成其他软件.由于来回切换的时间很短,我们感觉好像所有的程序都在同时运行,这也是多线程.多线程可以解决较多的用户访问同一个服务时压力过大的问题,可以更充分的利用计算机的性能. 多线程的问题 多线程的好处很多,可是相应的也出现了一些问题.其中最常见