一直以来对于Synchronized都比较迷惑,尤其还对于ReentrantLock并不了解他们之间的区别,今天闲来无事,学习了。

1,为什么要使用Synchronized

首先看Synchronized关键字的作用,以及我们为什么要用Synchronized,需求在哪里?

在程序开发过程中,当遇到多线程并发情况时,对于一些临界资源的访问便成为了一个问

题:

一是有可能导致错误的结果(进程A尚未执行完毕,进程B强行进入改变了之前的变量,此时

A再做处理结果肯定是错误的)。例如下面,A,B两进程同时开始运行,A

//继承Runnable接口 实现run方法 打印count值
public class test implement Runnable{//计数器初始为0int count = 0;//count自增@overridepublic void run(){system.out.println(Thread.currentThread().getName()+"_running start count:"+count);count++;Thread.sleep(100);system.out.println(Thread.currentThread().getName()+"_running end count:"+count);}
}
//若此时有A,B两个线程同时要访问
//A线程要循环100次结果
复制代码

二是有可能导致死锁或者饥饿现象发生,饥饿现象即A进程加锁之后进入临界区之后,中间

发生了异常或者执行时间无限循环,未能正确退出临界区释放锁,排队的B进程永远无法获取资

源,整个系统gg。而死锁就好比A拿一只筷子,B拿一只筷子,但是各自又都不放弃自己手中的

筷子,故谁也不能组成一双筷子,于是谁也吃不了饭。

所以我们需要对线程访问资源做一定的处理。

2,Synchronized实现的基本原理

2.1 线程的概念以及线程同步简单的实现

在计算机操作系统中,进程作为资源分配和任务调度的基本单位,在线程出现之后,线程便

成为了操作系统调度的基本单位。而一个进程内会有多个线程,同一个进程内的线程共享这个

进程里的所有资源,那么为了合理的访问这些共享资源,需要理解进程(线程同理)的同步以及互

斥,及PV操作和信号量机制。PV操作就是一种对于临界资源访问的处理方法。

P(signal);     //加锁
do something;  //访问临界资源
V(signal);     //释放锁复制代码

我们可以通过一个最简单的例子(不考虑死锁 饥饿问题,只是举个例子)来实现它:

//一个书架上只有一本书 一次只能拿一本 剩下的人等待
//P操作实现 实际复杂的多 此方法仅仅为基础原理
P(mutex){if (mutex>0){mutex--;}
}
//V操作实现
V(mutex){mutex++;
}
//首先定义互斥信号量 mutex
int mutex = 1;
//对于读者
Reader:
while(true){P(mutex);   //首先mutex-1 加锁 Reading;    //阅读V(mutex);   //mutex+1 释放临界资源:书
}复制代码

当然以上只是基本原理,Java中的Synchronized实现更为复杂,但是基本原理和目的大致相

同的。

2.2 Synchronized的底层实现

在Java早期版本中,Synchronized的实现是利用操作系统内核的功能来实现,由于线程若实

现加锁,必须从用户态转为核心态,才能使操作原子化,但是调用核心态耗时较多,导致早期

版本的Synchronized效率低下,自从Java1.6开始,HotSpot对于底层JVM不断优化,使得

Synchronized的性能已经有了不错的效率表现。

现在Synchronized的功能实现的基础是Java的对象头和Monitor。

首先来说说Java对象头是什么鬼,每一个Java类在加载时候,JVM会给这个类创建一个

KClass实例用来保存该类的信息和数据,当在程序中new一个该类的对象时候,JVM会创建一

个OopDesc对象,此对象中包含了java的对象头和实例化数据。

class oopDesc {friend class VMStructs;private:volatile markOop  _mark;union _metadata {wideKlassOop    _klass;narrowOop       _compressed_klass;} _metadata;
}
复制代码

其中的_mark就是Mark Word即标记字段,一般用存储对象中的哈希码(HashCode)、GC分

代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等信息。

而其中的_metadata是指向原有类的指针,代表了此对象是什么类型,哪一个类的实例。

而Monitor顾名思义即监视器,Java中每一个类都有唯一的一个Monitor,主要是记录当前对

象的状态信息。当多线程争取锁的时候,首先所有的线程会进入一个竞争队列ContentionLIst

中,之后有资格满足条件的进入EntryList等待获取锁,当获取锁时,Monitor中的Owner属性会

设置为当前线程,count也会+1,若线程调用wait()方法,该线程会进入waitset集合中等待唤

醒,并且设置count-1,owner置为NULL。若直接运行完毕,则exit()退出。

当java文件编译为class文件之后,在JVM中执行的过程。第33行和第109行分别显示

monitorenter.monitorexit表示进入监视器,退出监视器。以此作为tag

        33: monitorenter34: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;37: new           #12                 // class java/lang/StringBuilder40: dup41: invokespecial #13                 // Method java/lang/StringBuilder."<init>":()V44: invokestatic  #2                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;47: invokevirtual #3                  // Method java/lang/Thread.getName:()Ljava/lang/String;50: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;53: ldc           #18                 // String _synObjectBlock_start55: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;58: invokevirtual #16                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;61: invokevirtual #17                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V64: ldc2_w        #19                 // long 100l67: invokestatic  #21                 // Method java/lang/Thread.sleep:(J)V70: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;73: new           #12                 // class java/lang/StringBuilder76: dup77: invokespecial #13                 // Method java/lang/StringBuilder."<init>":()V80: invokestatic  #2                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;83: invokevirtual #3                  // Method java/lang/Thread.getName:()Ljava/lang/String;86: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;89: ldc           #22                 // String _synObjectBlock_end91: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;94: invokevirtual #16                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;97: invokevirtual #17                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V100: goto          108103: astore_2104: aload_2105: invokevirtual #24                 // Method java/lang/InterruptedException.printStackTrace:()V108: aload_1109: monitorexit
复制代码

2.3 Synchronized优化

在JDK1.6之后,HotSpot对于Synchronized进行了大量优化,添加了自旋锁,自适应自旋

锁,锁消除,锁粗化等,并且划分了无锁状态、轻量锁、偏向锁、重量级锁四种状态。

自旋锁之所以被引入,是因为对于程序运行期间,大部分线程对于锁的持有时间都是非常短

暂的,而每次当一个线程争取到锁,虽然只占用了很短暂时间,但是另一线程仍要被阻塞。频

繁的阻塞唤醒,对于CPU而言要从用户态和核心态来回转变是非常消耗时间的,也就造成了效

率的低下。那么自旋锁就是让另一线程执行无意义的循环(自旋)只是为了暂时不进行阻塞,对于

先前持有锁的进程执行完毕后它立马就可以继续运行,提高了效率。但是自旋锁是应用肯定要

分情况而言,它的自旋缺点占用了CPU的时间,优点在于提高了效率但仅在持有锁线程执行时

间很短,很快就可以释放锁的前提下,如果持有锁迟迟不释放锁,那么此操作反而会浪费CPU

的资源,相当于做无用功还占着CPU。所以我们为自旋规定的时间限度,超过一定时间没有释

放锁,则停止自旋,被阻塞。

自适应自旋锁,就很高级哈哈。相比于自旋锁,多了“AI”,也就是更加聪明了。如果之前此

线程通过自旋提高效率,那么之后允许自旋的次数就更多。反之一样。那么随着程序的不断运

行,那么越来越多的监控数据就会让判断的准确度进一步提升,对于提高系统效率会大有帮

助。

锁消除,就是在某些情况下,JVM检测到此共享资源并不会存在竞争,如果不加锁,既不影

响运行,又能提高效率。那么如何知道不存在竞争呢?JVM一般是对于代码中变量的数据流向

进行分析。当然对于不存在竞争的代码,我们在编写过程中也不会给代码块加上同步啊。

锁粗化,当某一线程频繁进行对于某一资源的使用,那么就不断的加锁释放锁,这样相当的

浪费资源,降低了效率。那么此时JVM就会把加锁范围放大,例如把锁放置在for循环之外,让

此线程再访问的时候就不需要重复加锁释放锁。

以上只是简单的对于Synchronized的几个特性和底层如何去实现的原理简单介绍,还需要多

多去学习,有可能一些地方描述的不准确,会逐步修改,争取准确。

转载于:https://juejin.im/post/5c4d434cf265da610f6411d4

Java多线程之Synchronized详解相关推荐

  1. Java多线程之volatile详解

    Java多线程之volatile详解 目录: 什么是volatile? JMM内存模型之可见性 volatile三大特性之一:保证可见性 volatile三大特性之二:不保证原子性 volatile三 ...

  2. java多线程之ThreadLoal详解

    一.ThreadLocal简介 多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时.为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步 同步一般是 ...

  3. Java多线程之Synchronized和Lock的区别

    Java多线程之Synchronized和Lock的区别 目录: 原始构成 使用方法 等待是否可以中断 加锁是否公平 锁绑定多个条件Condition 小结:Lock相比较Synchronized的优 ...

  4. 多线程之callable详解

    多线程之callable详解 面试有人会问:线程的实现方式有几种? 很多人可能回答:2种,继承Thread类,实现Runnable接口. 很多忽略了callable这种方式. 也许有人知道callab ...

  5. Java并发编程之CyclicBarrier详解

    简介 栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生.栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行.闭锁用于等待事件,而栅栏用于等待其他线程. CyclicBarrier ...

  6. 并发编程专题——第二章(并发编程之Synchronized详解)

    日常中我们都会用到Synchronized关键字,但是面试就喜欢问这些,你说不重要吧,面试就不问了,你说重要吧,工作中除了高并发之外,很少能在业务代码中使用到的.所以笔者顶着风险,写下此篇对Synch ...

  7. Java并发编程之ConcurrentLinkedQueue详解

    简介 在并发编程中我们有时候需要使用线程安全的队列.如果我们要实现一个线程安全的队列有两种实现方式一种是使用阻塞算法,另一种是使用非阻塞算法.使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两 ...

  8. Java并发编程之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  9. JAVA多线程之Synchronized、wait、notify实例讲解

    一.Synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争 ...

最新文章

  1. 【ACM】杭电OJ 4548 美素数(二次打表)
  2. 线程安全的ConcurrentQueueT队列
  3. java基础----Runtime类的使用(一)
  4. Xilinx发布实时视频编码服务器
  5. how is sap-ui-core.js initialize the reqeust of sap-ui-core-dbg.js
  6. php mysql导入excel_如何从PHP导入Excel文件到mysql数据库
  7. long 雪花算法_雪花算法(SnowFlake)Java版
  8. 注册时,邮箱自动发送验证
  9. JVM快速调优手册v1.0
  10. 数据库系统原理教程-作业
  11. jetty-maven-plugin
  12. Ubuntu16.04和树莓派3B编译opencv3
  13. Nobook虚拟实验室完爆各种传统实验室
  14. 12-Qt5调用OpenCV4
  15. 其他,HC6800-EM3 V30原理图
  16. 【Unity3D应用案例系列】答题系统开发
  17. MVP从入门到...
  18. Android AudioFocus机制小结
  19. 人工智能训练师数加加标注培训系统正式上线
  20. 网络摄像机显示管理服务器离线,有看头为什么总是离线 摄像头网络异常解决方法...

热门文章

  1. Windows XP客户端加域操作手册下
  2. 通过迁移的方式解决Active Directory服务器问题之6
  3. 微信JS SDK Demo 官方案例
  4. AttachDispatch
  5. Apache Cassandra 开源数据库软件修复高危RCE漏洞
  6. 大数据工程师的简易解释
  7. js中变量名提升和函数名提升
  8. Activity实现 高亮显示活动节点,和所有已完成过的节点
  9. gradle 构建测试
  10. innodb 共享表空间 转 独立表空间 详细说明