JAVA基础知识系列---进程、线程安全
1 相关概念
1.1 临界区
保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只允许一个线程对资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后,其他所有试图访问临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的
1.2 互斥量
互斥量和临界区很相似,只能拥有互斥对象的线程才能具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下次共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后可以访问资源。互斥量比临界区复杂,因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
1.3 管程/信号量
管程和信号量是同一个概念。指一个互斥独占锁定的对象或称为互斥体。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定,他必须进入管程。所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。这些其他的线程被称为等待线程。一个拥有管程的线程如果愿意的话可以再次进入相同的管程(可重入性)
1.4 CAS操作
CAS操作(compare and swap)CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。下面来看一下AtomicInteger是如何利用CAS实现原子性操作的。
1.5 重排序
编译器和处理器为了提高性能,而在程序执行时会对程序进行重排序。他的出现是为了提高程序的并发度。从而提高性能;但是对于多线程程序,重排序可能会导致程序执行的结果不是我们需要的结果,重排序分为编译器和处理器俩个方面。而处理器重排序包括指令级重排序和内存重排序。
小节
在java中,所有的变量(实例字段,静态字段,构成数组的元素,不包括局部变量和方法参数)都存储在主内存中,内个线程都有自己的工作内存,线程的工作内存保存被线程使用到的变量的主内存副本拷贝。线程对变量的所有操作都必须在工作内存中进行,为不能直接读写主内存的变量。不同线程之间也不恩能够直接访问对方工作内存中的变量,线程间比变量值的传递通过主内存来完成。
JAVA中线程安全相关关键字及类
主要包括:synchronized,Volitile,ThreadLocal,Lock,Condition
2.1 Volitile
作用:
1)保证了心智能立即存储到主内存才,每次使用前立即从主内存中刷新
2)禁止指令重排序优化
Volitile关键字不能保证在多线程环境下对共享数据的操作的正确性,可以使用在自己状态改变之后需要立即通知所有线程的情况下,只保证可见性,不保证原子性。即通过刷新变量值确保可见性。
Java中synchronized和final也能保证可见性
synchronized:同步快通过变量锁定前必须清空工作内存中的变量值,重新从主内存中读取变量值,解锁前必须把变量值同步回主内存来确保可见性。
final:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this引用传递进去,那么在其他线程中就能看见final字段的值,无需同步就可以被其他线程正确访问。
2.2 synchronized
把代码块声明为synchronized,有俩个作用,通常是指改代码具有原子性和可见性。如果没有同步机制提供的这种可见性,线程看到的共享比那里可能是修改前的值或不一致的值,这将引发许多严重问题。
原理:当对象获取锁是,他首先是自己的高速缓存无效,这样就可以保证直接从主内存中装入变量,同样在对象释放锁之前,他会刷新其高速缓存,强制使已做的任何更改都出现在主内存中,这样会保证在同一个锁上同步的俩个线程看到在synchronized块内修改的变量的相同值。
synchronized释放由JVM自己管理。
存在的问题:
1)无法中断一个正在等待获得锁的线程
2)无法通过投票得到锁,如果不想等待下去,也就没法得到锁
3)同步还需要锁的释放只能在与获得锁所在的堆栈帧相同的堆栈中进行,多数情况下,这没问题(而且与一场处理交互的很好),但是,确实存在一些非块结构的锁定更适合情况。
2.3 Lock
Lock是有JAVA编写而成的,在java这个层面是无关JVM实现的。包括:ReentrantLock,ReadWriteLock。其本质都依赖于AbstractQueueSynchronized类。Lock提供了很多锁的方式,尝试锁,中断锁等。释放锁的过程由JAVA开发人员自己管理。
就性能而言,对于资源冲突不多的情况下synchronized更加合理,但如果资源访问冲突多的情况下,synchronized的性能会快速下降,而Lock可以保持平衡。
2.4 condition
Condition将Object监视器方法(wait,notify,notifyall)分解成截然不同的对象,以便通过这些对象与任意Lock实现组合使用,为每个对象提供多个等待set(wait-set),,其中Lock替代了synchronized方法和语句的使用,condition替代了Object监视器方法的使用。Condition实例实质上被你绑定到一个锁上。要为特定Lock实例获得Condition实例,请使用其newCondition()方法。
2.5 ThreadLock
线程局部变量。
变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本,这种情况下TreadLocal就非常有用。
应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始值的时候,最适合使用TreadLocal。
事实上,从本质上讲,就是每个线程都维持一个MAP,而这个map的key就是TreadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那就肯定不存在线程安全的问题。总体来讲,TreadLocal这个变量的状态根本没有发生变化。它仅仅是充当了一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮助我们做了这个事情。
使用TreadLocal维护变量时,TreadLocal为每个使用该变量的线程提供独立地变量副本,所以每一个线程都可以独立地改变自己的副本,而不会英语其他线程所对应的副本。从线程的角度看,目标变量对象是线程的本地变量,这也是类名中Local所需要表达的意思。
TreadLocal的四个方法:
void set(Object val),设置当前线程的线程局部变量的值
Object get()返回当前线程所对用的线程局部变量。
void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,线程结束后,局部变量自动被GC
Object initValue() 返回该线程局部变量的初始值,使用protected修饰,显然是为了让子类覆盖而设计的。
线程安全的实现方式
3.1 互斥同步
在多线程访问的时候,保证同一时间只有一条线程使用。
临界区,互斥量,管程都是同步的一种手段。
java中最基本的互斥同步手段是synchronized,编译之后会形成monitorenter和monitorexit这俩个字节码指令,这俩个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象,还有一个锁的计数器,来记录加锁的次数,加锁几次就要同样解锁几次才能恢复到无锁状态。
java的线程是映射到操作系统的原生线程之上的,不管阻塞还是唤醒都需要操作系统的帮助完成,都需要从用户态转换到核心态,这是很耗费时间的,是java语言中的一个重量级的操作,虽然虚拟机本身会做一点优化的操作,比如通知操作系统阻塞之前会加一段自旋等待的过程,避免频繁切换到核心态。
3.2 非阻塞同步
互斥和同步最主要的问题就是阻塞和唤醒所带来的性能的问题,所以这通常叫阻塞同步(悲观的并发策略).随着硬件指令集的发展,我们有另外的选择:基于冲突检测的乐观并发策略,通俗讲就是先操作,如果没有其他线程争用共享的数据,操作就成功,如果有,则进行其他的补偿(最常见的就是不断的重试)。这种乐观的并发策略许多实现都不需要把线程先挂起,这种同步操作被称为非阻塞同步。
3 无同步
部分代码天生就是线程安全的,不需要同步。
1)可重入代码:纯代码,具有不依赖存储在堆上的数据和公用的系统资源,用到的状态量都由参数中传入,不调用非可重入的方法等特征,它的返回结果是可以预测的。
2)线程本地存储:把共享数据的可见性范围限制在同一个线程之内,这样就无需同步也能保证线程之间不出现数据争用问题。可以通过java.lang.TreadLocal类来实现线程本地存储的功能。
JAVA基础知识系列---进程、线程安全的更多相关文章
- Java基础知识系列——String
最近晚上没有什么事(主要是不加班有单身),就复习了一下Java的基础知识.我复习Java基础知识主要是依据Java API和The Java™ Tutorials. 今天是第一篇,复习了一下Strin ...
- JAVA基础知识之多线程——线程池
线程池概念 操作系统或者JVM创建一个线程以及销毁一个线程都需要消耗CPU资源,如果创建或者销毁线程的消耗源远远小于执行一个线程的消耗,则可以忽略不计,但是基本相等或者大于执行线程的消耗,而且需要创建 ...
- JAVA基础知识之多线程——线程组和未处理异常
线程组 Java中的ThreadGroup类表示线程组,在创建新线程时,可以通过构造函数Thread(group...)来指定线程组. 线程组具有以下特征 如果没有显式指定线程组,则新线程属于默认线程 ...
- JAVA基础知识之多线程——线程通信
传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题. wait():释放当前线程的同步监视控制器,并 ...
- JAVA基础知识之多线程——线程同步
线程安全问题 多个线程同时访问同一资源的时候有可能会出现信息不一致的情况,这是线程安全问题,下面是一个例子, Account.class , 定义一个Account模型 package threads ...
- Java基础知识系列——Exception
异常在编程中使用频率非常非常的高,在Java中异常的基类是Exception. 下面就介绍一下Java中的异常: 1.结构 try{ //捕获try里的异常 }catch( Exception e){ ...
- Java基础知识系列——目录操作
Java对目录操作的许多方法与上一篇文件操作的方法很多是一样的. java.io.File file = new File( "D:\1\2\3\4"); 1.递归创建目录 fil ...
- Java基础知识系列——文件操作
对文件进行操作在编程中比较少用,但是我最近有一个任务需要用到对文件操作. 对文件有如下操作形式: 1.创建新的文件(夹) File fileName = new File("C:/myfil ...
- Java基础知识系列——日期
日期类型也是在编程中经常用到的一种数据类型. Java中的日期类型为Date. 另外需要记住三个类: java.text.SimpleDateFormat; java.util.Calendar; j ...
随机推荐
- Alamofire 4.0 迁移指南
Alamofire 4.0 是 Alamofire 最新的一个大版本更新, 一个基于 Swift 的 iOS, tvOS, macOS, watchOS 的 HTTP 网络库. 作为一个大版本更新, ...
- js架构设计模式——你对MVC、MVP、MVVM 三种组合模式分别有什么样的理解?
你对MVC.MVP.MVVM 三种组合模式分别有什么样的理解? MVC(Model-View-Controller)MVP(Model-View-Presenter)MVVM(Model-View-V ...
- MySQL生产库开发规范
MySQL开发规范 文件状态: [ ] 草稿 [√] 正式发布 [ ] 正在修改 文件标识: 当前版本: V1.0 作 者: 贺磊 完成日期: 2016-05-24 变更记录 序号 ...
- Java线程:线程安全类和Callable与Future(有返回值的线程)
一.线程安全类 当一个类已经很好的同步以保护它的数据时,这个类就称为线程安全的.当一个集合是安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候,第二个线程也来 ...
- xhtmlrenderer渲染pdf,中文换行
在实际开发中,发现在table中显示中文,渲染出来的pdf,中文内容不自动换行.经过搜索发现了一种解决方案,如下: 重写Breaker,修改right计算方式 /* * Breaker.java * ...
- Spring之循环依赖
转:http://my.oschina.net/tryUcatchUfinallyU/blog/287936 概述 如何检测循环依赖 循环依赖如何解决 Spring如何解决循环依赖 主要的几个缓存 主 ...
- Linux 服务器设置成支持中文
Linux 服务器设置成支持中文 由于服务器默认是不支持中文的.所以一般需要单独设置一下. 检查本机已有的语言包 locale -a 默认是没有中文的,所以会显示: C C.UTF-8 POSIX e ...
- 设计社区Dribbble VS. Bēhance,你选谁?
Behance和Dribbble都是主流的设计作品分享平台,为广大设计师同胞们带来了莫大的便利,所以很多设计师通常两个社区都会关注.很多设计师在展示个人信息的时候,通常也会把这两个平台的链接放到个人资 ...
- 消费创富会开发模式系统App
消费创富会系统定制开发,消费创富会网页开发模式,消费创富会开发软件,消费创富会系统APP开发,消费创富会平台模式开发,专业开发微信商城分销.公排.全返.分红.互助等模式定制开发,APP.网页版.微信端 ...
- 微端启动器LAUNCHER的制作之MFC版二(下载)
用了C#再回来用C++写真的有一种我已经不属于这个世界的感觉.C++的下载就没有C#那么方便了,我用的是libcurl.dll,官网上下载的源码自己cmake出来编译的,c++的库引用有debug和r ...