程序员谈话系列——今天我们来谈一下synchronized……

说起synchronized相信大家都很熟悉,就这个东西叫做互斥锁,平时呢可以帮助我们实现譬如线程安全的问题。那么今天咱们就来深入底层,好好的谈一下synchronized的原理和应用

一.谈一下对于synchronized的了解

synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被他修饰的方法或者代码块在任意时刻只有一个线程执行。但是这个关键字虽然从功能上看还不错,但是在JDK6之前synchronized是不被人喜欢的,因为它是重量级锁,效率非常低。这是因为监视器锁monitor是依赖于底层的操作系统的Mutex lock来实现的,Java的线程是映射到操作系统的原生线程之上的,如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的转化需要从用户态转到内核态,这个状态之间的转化需要相对比较长的时间,时间成本相对较高。但是jdk6之后,Java官方从JVM层面对synchronized进行了较大的优化,所以现在的效率还算可以,至于优化方式,就包括了自旋锁,适应性自旋锁,锁销除,锁粗化,偏向锁等来减少锁系统的开销。

二.谈一下如何使用synchronized关键字?

synchronized主要有三种用法:

1)修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。

2)修饰静态方法:   给当前的类加锁,作用域所有类的对象实例,因为静态成员属于类成员。所以如果一个线程A调用了一个实例对象的非静态synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法是允许的,这样不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的锁,而访问非静态synchrinized方法占用的是当前实例对象锁。

3)修饰代码块:制定加锁对象,对给定的对象加锁,进入同步代码块之前要获得给定对象的锁。

所以修饰静态方法和代码块都是给类上锁,修饰实例方法是给实例对象上锁。尽量不要使用synchronized(String a)因为JVM中字符串常量池具有缓存功能。

3.谈一下单例模式和双重检验锁方式实现单例模式的原理

public class Singleton{
private volatile static Singleton uniqueInstance;
private Singleton(){
}
public static Singleton getUniqueInstance(){
//先判断对象是否已经实例过,没有实例过才进入加锁代码
if (uniqueInstance==null){
synchronized (Singleton.class){
if(uniqueInstance==null){
uniqueInstance =new Singleton();
}
}
}
return uniqueInstance;
}
}

需要注意uniqueInstance采用volatile修饰也是非常重要的。uniqueInstance=new Singleton();这段代码其实分为了三步执行:

1.为uniqueInstance分配内存空间

2.初始化uniqueInstance

3.将uniqueInstance指向分配的内存地址。

但是由于JVM指令重排的特性,执行顺序可能变成了1,3,2。这样在多线程环境下会导致一个线程获得还没有初始化的实例。例如线程1执行1和3,此时线程2调用getUniqueInstance()后发现uniqueInstance不为空,因此返回了uniqueInstance但此时UniqueInstance还没有初始化,所以运行就不会正常,但是加上volatile可以禁止JVM指令重排。保证在多线程的条件下也可以正常运行。

三.讲一下synchronized关键字的底层原理。

synchronized底层原理属于jvm层面

1)synchronized同步代码块

public class SynchronizedDemo{
public void method(){
synchronized(this){
System.out.println("synchronized 代码块");
}
}
}

通过JDK自带的javap命令可以查看SynchronizedDemo类相关的字节码文件。首先切换到类的对应目录执行javac Syschronzied.java的命令生成编译后的.class文件,然后执行javap -c -s -v -l  Syschronized.class。可以看到synchronized使用的monitorenter和monitorexit命令实现的,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令指向的是代码块结束的位置。也就是说,线程试图获取锁也就相当于获取monitor(monitor对象存在于每个Java对象的对象头中,synchronized就是通过这种方式获取锁的,也是为什么java中任意对象可以作为锁的原因)的持有权,当计数器为0时,则可以获取成功,之后将计数器加1,在执行monitorexit的命令之后,计数器就设为0,表明了锁已经被重新释放,如果获取失败就需要当前线程阻塞等待,知道锁被另外一个线程释放为止。

2)synchronize修饰方法的时候。

在修饰方法的时候,底层代码相对简单,就是加入了ACC_SYNCHRONIZED标识,这个标识指明了该方法是一个同步方法,JVM通过该访问标识来确定是不是一个同步方法从而执行相应的调用。

四.谈一谈JDK6之后synchronized关键字底层的优化

JDK1.6对锁的实现引入了大量的优化,如偏向锁,轻量级锁,自旋和适应性自旋等等来减少锁的开销。锁主要有四种形态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。锁可以升级不可以降级,这种策略是为了可以提高获得锁和释放锁的效率。这个想要理解深入的化,建议去看一下《深入了解Java虚拟机第二版》,一定会受益匪浅。

五.谈谈synchronized和reentrantlock的区别

1)两者都是可重入锁。

可重入锁的概念:自己可以再次获取自己的内部锁,比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获得这个对象的锁的时候还是可以获取的,如果不可锁冲入的话,就会造成死锁。每一个线程每次获得锁,锁的计数器都会自增1,所以想要等到锁的计数器下降为0时才能释放锁。

2)synchronized依赖于JVM而reentrantLock依赖于API

前面已经说了太多synchronized在JVM当中的实现和优化了,那么为什么说reentranLock是在API层面呢,就是JDK层面,因为它需要lock()和unlock()方法配合try/finally语句块来完成。所以我们可以通过他的源代码来看他是如何实现的。

3)Reentrantlock比synchronized增加了很多高级的功能。

主要说来有三点,1.等待可中断2.可实现公平锁3.可实现选择性通知(锁可以绑定多个条件)

Reentrantlock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制,也就是说正在等待的线程可以选择放弃等待,改为处理其他的事情。

Reentrantlock可以指定是公平锁还是非公平锁,也就是先等待的线程先获得。这是通过ReetranLock(boolean fair)来制定的。

synchronized中的wait()和notify()/notifyAll()方法相结合可以实现等待通知机制,但是Reentrantlock需要记住Condition接口和newCondition()方法。这个接口是JDK1.5之后才有的,具有很好的灵活性。比如可以实现一个Lock对象中可以创建多个Condition实例,即对象监视器。线程对象可以注册在制定的Condition中,从而有选择的进行线程通知,在调度线程上更加灵活,在使用notify()方法进行通知时,被通知的线程是JVM选择的,而ReetrantLock可以和Condition实例结合实现选择性通知。而synchronized只有一个Condition实例,所以执行signAll()方法的话就会通知所有等待的线程,很费事。

原文地址:https://www.cnblogs.com/ffdsj/p/12396199.html

时间: 2024-11-08 22:34:59

程序员谈话系列——今天我们来谈一下synchronized……的相关文章

程序员谈话系列————Redis性能分析

在一些网络服务的系统中,Redis 的性能,可能是比 MySQL 等硬盘数据库的性能更重要的课题.比如微博,把热点微博[1],最新的用户关系,都存储在 Redis 中,大量的查询击中 Redis,而不走 MySQL. 那么,针对 Redis 服务,我们能做哪些性能优化呢?或者说,应该避免哪些性能浪费呢? Redis 性能的基本面 在讨论优化之前,我们需要知道,Redis 服务本身就有一些特性,比如单线程运行.除非修改 Redis 的源代码,不然这些特性,就是我们思考性能优化的基本面. 那么,有哪

程序员谈话系列——————解开AQS的神秘面纱

一,谈一谈什么是AQS AQS是一个用来创建锁和同步器的框架,使用AQS能够简单且高效的构造出应用广泛的大量的同步器,比如常用的ReentrantLock,Semaphore‘,其他的诸如ReentrantReadWriteLock,FutureTask等等皆是基于AQS非常轻松容易的构造出符合我们自己需求的同步器. 二,AQS原理分析 AQS核心思想是,如果被请求的共享资源空闲,那么将请求资源的线程设置为有效线程,并且将共享资源设为锁定状态.如果被请求的共享资源被占用,那么就需要一套线程阻塞等

程序员谈话系列——关于redis的一些理解(二)

一,redis的数据类型都有什么? String hash list set sortedset HyperLogLog Pub/Sub 二,缓存雪崩,击穿,传统. 雪崩:一些热点数据都会做缓存,一般会同时进行定时任务刷新.如果key的失效时间时,大量的用户请求涌入会直接落到数据库上,数据库一般会报一下警,但很有可能没有反应就直接挂了.当然重启数据库也会直接被流量打死.比如如果打挂的是一个用户服务的库,那么依赖这个库的接口都会报错,如果不做熔断的话会瞬间挂到一片. 做法: 1,为了避免缓存雪崩,

程序员成长系列(一):手里必须有一套自己的框架

转眼间工作三年了,对于程序员来说,三年算是这个行业的第一道坎.回首过往,大多都是进行重复性的工作,偶尔的也会进行技术攻关,但是没多久就忘记了,除了找工作的时期,平时是不会理会的. 不知不觉间发现自己这是在通货膨胀中不断贬值. 只要还有精力,学个五年十年那都不是问题,问题是这五年十年都学了些什么.经过多天的考虑,有必要整理一下这三年的所学所得,而且手里必须得有个拿得出手的东西.于是有必要整理一套自己的框架,而且吃透弄熟,把一些常用的功能组件都剥离出来,对以后无论是做管理还是搞技术都有莫大的帮助.

程序员表白系列源码

程序员表白系列源码 这里只是借花献佛 曾经也有人进行过整理 http://download.csdn.net/album/detail/1501

程序员资源系列(不断完善中)

1. 程序员读书资源网址: 1)红黑联盟的读书频道 2. PHP资源系列: 1)PHP100中文网 2)第一PHP社区 3. 时间管理系列: 1)GTD 4. 娱乐学习系列: 1)在线读书

【程序员感悟系列】 由一点业务说开去

最近的工作不是很忙,我也趁着这个机会多读了一些技术的书籍.比如刚读完的<大话设计模式>,以将故事的形式讲述了设计模式的方方面面,感觉还是不错的.现在看的一本是英国人写的<企业应用架构模式>.对于web的企业级应用,还是挺有借鉴的意义的. 我也觉得,12年毕业到现在,自己的技术一定要精进.所以多看设计模式,企业架构.然后,一个业务上的小事改变了我的看法,公司的网站的线上出现了一个数据的异常,显示的时间信息是 1970-01-01,一看就知道是数据库存储的是0,所以php解析出来的是u

程序员人生系列

程序员的上升空间在哪里 程序员的上升空间在哪里 我见证过许多的程序员的成长,他们很多人在进入成熟期之后,技术上相对较高,一般项目开发起来比较自信,没有什么太大的困难,有的职位上也有所提升,成了项目经理.设计师,有的甚至是到了管理者的位置.又经过很长时间,这些程序员却在也没有什么变化和发展,工作稳定.情绪稳定,好像一切都在按部就班地进行着.有的程序员满足于现在的收入和职位,安于现状:有的程序员却在安于现状的同时,苦苦思索... 今天我们为什么不成功 今天我们为什么不成功? 1.首先我们没有定义好自

程序员面试,为什么不要大谈高并发?

作为一个看过几千份简历,面试过几百人的面试官,常常会看到简历中有如下文字: 对业务逻辑解耦,高并发等有比较深入的研究和丰富的开发实战经验 对解决高并发问题有深入理解 熟悉大并发技术,如:反向代理.负载均衡.Keepalived 而当我在面试中,问及对方的职业规划的时候,也有一大半人会回答 希望将来可以处理高并发业务 希望学习高并发相关技术 希望开发数千万/数亿级别并发的应用 但是当我问及以下问题的时候,绝大多数人都会麻爪: 负载均衡有几种分配方式?(大概不到1/10的简历提及高并发的人能答出来)