什么是线程安全

当多个线程同时访问一个对象的时候,不需要考虑什么额外的操作就能获取正确的值,就是线程安全的。

线程安全的程度

1.不可变

不可变的对象一定是线程安全的,因为值始终只有一个。

final,这种安全是最直接最纯粹的。

2.绝对线程安全

不管运行时环境如何,调用者都不需要任何额外的同步措施。

往往JDK中说自己是线程安全的,大多不是绝对安全的。

需要付出巨大的代价,往往不需要绝对安全。

3.相对线程安全

通常所说的线程安全,就是相对线程安全,需要保证对象单次的操作是安全的,不需要额外的保障措施,但是对于一些特定顺序的额外调用,就会需要额外的同步措施。

// 读线程
Thread t1 = new Thread( new Runnable(){
public void run(){
for(int i=0; i < vector.size(); i++){
System.out.println( vector.get(i) );
}
}
}).start(); // 写线程
Thread t2 = new Thread( new Runnable(){
public void run(){
for(int i=0; i < vector.size(); i++){
vector.remove(i);
}
}
}).start();

如果上述代码中,读循环被写线程打断了,还删除了所有的元素,但是读循环的越界下标还是原来的数组大小,这样就会出现越界异常。

需要对整个循环进行加锁。其实也可以采用迭代器进行遍历,如果在迭代的时候,集合被操作了,就会抛出异常。

// 读线程
Thread t1 = new Thread( new Runnable(){
public void run(){
synchronized( vector ){
for(int i=0; i<vector.size(); i++){
System.out.println( vector.get(i) );
}
}
}
}).start(); // 写线程
Thread t2 = new Thread( new Runnable(){
public void run(){
synchronized( vector ){
for(int i=0; i<vector.size(); i++){
vector.remove(i);
}
}
}
}).start();

4.线程对立

无论怎么同步,都不能实现线程安全,就是线程对立。

这是有害的,要避免。

线程安全的实现方法

互斥同步

是最常见最主要的并发手段。同步是指同一时刻数据只被一条线程使用。互斥是实现同步的手段。

属于阻塞同步。

优先考虑使用synchronized:

  • synchronized是Java语法层面的同步,清晰简单。
  • Lock需要求finally中释放锁,不然出现异常就可能无法释放锁。

synchronized

最基本的互斥同步手段是synchronized,通过monitorenter和monitorexit指令实现。

  1. 编译器在同步块的开始和技术位置添加monitorenter和monitorexit指令;
  2. 这两个对象都需要一个reference类型的参数来指明要锁定和解锁的对象。
  3. 如果没有指明对象,那么把当前对象或者类作为锁对象。
  4. 这是一把可重入锁。会阻塞后面想要获取锁的线程。

当有线程要访问这段代码的时候,会先去尝试获取锁,如果锁的计数器值是0,说明可以获得锁,然后执行monitorenter将计数器值加一,获得锁。在执行代码结束的时候,执行monitorexit将锁的计数器值加1,释放锁。

ReetrantLock

重入锁是Lock接口的最常见形式。是可重入的。相比synchronized具有更多高级功能。

  • 等待可中断:线程长期无法获得锁的时候,可以放弃等待。
  • 公平锁:按照等待的顺序释放锁。
  • 绑定多个条件:synchronized可使用wait/notify来实现等待/通知机制,但一个synchronized同步块只能使用一次,若要使用多次,就需要嵌套同步块;但ReentrantLock可以通过newCondition创建多个条件。

非阻塞同步

是一种乐观锁,不对共享数据进行加锁,而是直接操作,再进行判断措施。

JUC中各种整形原子类的自增、自减等操作就使用了CAS。

CAS操作:存在三个值,分别是共享变量V、预期旧值A、新值B,弱V与A相同,则将V更新成B,不然就循环比较,直到更新完成。

可能出现ABA问题。通过modCount字段建立修改版本,防止ABA。

无同步方案

下面两种情况不需要同步:

  • 可重入代码:某个方法,只要输入的值一样,结果就是一样的,线程怎么切换,值还是不变的。
  • 线程本地存储:把所有共享变量的操作都放在一个线程中,就不存在多线程访问的安全问题了。

web服务器就是采用线程本地存储,把分别把每个请求封装在一个线程中处理。

锁优化

自旋锁

作用

避免线程阻塞,而是不断使用CPU循环去获取锁,减少线程切换时候上下文切换的开销。

问题

如果长时间不能获得锁,就是浪费CPU资源。

自适应自旋

根据以往自旋等待的时间,动态调整下次自旋的等待时间。

锁清除

移除不可能存在资源竞争处的锁,降低同步的开销。

锁粗化

如果虚拟机探测到对同一个对象反复加锁,则编译器会扩大锁的代码范围,从而降低锁的切换频率。

轻量级锁

原理

利用CAS方法取代互斥同步。

利用对象头的MarkWord进行CAS操作,具体原理可以看另一篇文章。
轻量级锁

与重量级锁比较

  • 重量级锁是悲观锁,总是假设会出现线程竞争,所以总是要进行互斥同步。
  • 轻量级锁是乐观锁,总是假设不会出现竞争,获取利用CAS操作来获得锁。

偏向锁

原理

当线程请求到锁对象后,将锁对象的状态标志位改为01,即偏向模式。然后使用CAS操作将线程的ID记录在锁对象的Mark Word中。以后该线程可以直接进入同步块,连CAS操作都不需要。如果出现其他线程的竞争,那么偏向锁被取消,进入轻量级锁。

与轻量级锁比较

  • 都是乐观锁。
  • 轻量级锁通过CAS操作代替互斥量,而偏向锁在无竞争的情况下取消了同步。

深入理解Java虚拟机(十)——线程安全与锁优化的更多相关文章

  1. 《java虚拟机》----线程安全和锁优化

    No1: 线程安全:当多个线程访问一个对象时,如果不用考虑这些线程在运行环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这 ...

  2. 《深入理解java虚拟机》学习笔记之编译优化技术

    郑重声明:本片博客是学习<深入理解Java虚拟机>一书所记录的笔记,内容基本为书中知识. Java程序员有一个共识,以编译方式执行本地代码比解释方式更快,之所以有这样的共识,除去虚拟机解释 ...

  3. 《深入理解Java虚拟机》笔记04 -- 并发锁

    Java虚拟机在操作系统层面会先尽一切可能在虚拟机层面上解决竞争关系,尽可能避免真实的竞争发生.同时,在竞争不激烈的场合,也会试图消除不必要的竞争.实现这些手段的方法包括:偏向锁.轻量级锁.自旋锁.锁 ...

  4. 深入理解JAVA虚拟机 晚期(运行期)优化(转载)

    这一章节的内容实用性不强 所以不再手打笔记 转载了一篇 原文地址是http://blog.csdn.net/qq_27350929/article/details/54837595 在部分的商用虚拟机 ...

  5. Java 虚拟机:互斥同步、锁优化及synchronized和volatile

    互斥同步 互斥同步(Mutual Exclusion & Synchronization)是常见的一种并发正确性保证手段.同步是指子啊多个线程并发访问共享数据时,保证共享数据在同一时刻只能被一 ...

  6. 深入理解java虚拟机(7)---线程安全 & 锁优化

    关于线程安全的话题,足可以使用一本书来讲解这些东西.<Java Concurrency in Practice> 就是讲解这些的,在这里 主要还是分析JVM中关于线程安全这块的内容. 1. ...

  7. 深入理解java虚拟机(6)---内存模型与线程 & Volatile

    其实关于线程的使用,之前已经写过博客讲解过这部分的内容: http://www.cnblogs.com/deman/category/621531.html JVM里面关于多线程的部分,主要是多线程是 ...

  8. 深入理解java虚拟机---虚拟机工具jconsole(十八)

    Jconsole,Java Monitoring and Management Console. Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到.它用于连接正在运行的本地或者远 ...

  9. 深入理解java虚拟机-第13章-线程安全与锁优化

    第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...

随机推荐

  1. Fiddler的一系列学习瞎记3

    Http: 不安全.可以很容易被拦截,或者其他的嗅探工具发现.怎么样做到安全?起码一下两点: 1.浏览器和we服务器之间的内容应该只有浏览器和web服务器能看到通信内容. 2.Http请求和Http的 ...

  2. python之路《八》装饰器

    装饰器是个好东西啊 那么装饰器是个什么样的东西呢,他又能做些什么呢? 1.为什么装饰器 当我们一个程序已经构建完成,并且已经发布出去了,但是现在需要增加一个活动,例如淘宝给你发送一个今日优惠,或者开启 ...

  3. 基于docker部署ceph以及修改docker image

    前言 容器和ceph的结合已经在一些生产环境当中做了尝试,容器的好处就是对运行环境的一个封装,传统的方式是集成为ISO,这个需要一定的维护量,而容器的相关操作会简单很多,也就有了一些尝试,个人觉得如果 ...

  4. 利用虚拟化环境虚拟nvme盘

    前情介绍 SPDK SPDK的全称为Storage Performance Development Kit ,是Intel发起的一个开源驱动项目,这个是一个开发套件,可以让应用程序在用户态去访问存储资 ...

  5. Django实战总结 - 快速开发一个数据库查询工具

    一.简介 Django 是一个开放源代码的 Web 应用框架,由 Python 写成. Django 只要很少的代码就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的 Web 服务 ...

  6. 用十一张图讲清楚,当你CRUD时BufferPool中发生了什么!以及BufferPool的优化!

    一.收到了大佬们的建议 1.篇幅偏短,建议稍微加长一点. 这点说的确实挺对,有的篇幅确实比较短,针对这个提议我会考虑将相似的话题放在一篇文章中.但是这可能会导致我中断每天更新的步调,换成隔几天发一篇的 ...

  7. 利用代理IP池(proxy pool)搭建免费ip代理和api

    先看这里!!!---->转载:Python爬虫代理IP池(proxy pool) WIIN10安装中遇到的问题: 一.先安装Microsoft Visual C++ Compiler for P ...

  8. FL Studio CPU面板讲解

    在FL Studio中,其CPU面板主要是由CPU使用表.内存使用表和复音数这几个部分组成的.这些对刚接触这款音乐制作软件的同学来说是非常陌生的吧!因为不知道这些是什么,主要的作用是什么.所以小编这里 ...

  9. 商业智能(BI)可视化大屏的设计及使用原则

    信息时代,数据是一种可贵的资源,我们可能经常听到的一句话就是:用数据说话.但是,在没有进行系统化整理之前,数据不过只是一串串冰冷的数字,我们很难从大量的数据中获取到有价值的信息.只有通过合适的可视化工 ...

  10. SFTP 服务器cd() 方法和 ls() 方法说明

    方法说明: cd():这个方法用于进入某个目录下. 默认情况,当连接SFTP服务器成功后直接进入用户目录,比如我连接自己本机SFTP服务器后进入/Users/mac目录.cd() 方法进入每一个目录都 ...