JAVA基础知识系列---进程、线程安全

1.1 临界区

保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只容许一个线程对资源进行访问。若是有多个线程试图同时访问临界区,那么在有一个线程进入后,其余全部试图访问临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其余线程能够继续抢占,并以此达到用原子方式操做共享资源的目的java

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)保证了心智能当即存储到主内存才,每次使用前当即从主内存中刷新this

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.3 无同步

部分代码天生就是线程安全的,不须要同步。

1)可重入代码:纯代码,具备不依赖存储在堆上的数据和公用的系统资源,用到的状态量都由参数中传入,不调用非可重入的方法等特征,它的返回结果是能够预测的。

2)线程本地存储:把共享数据的可见性范围限制在同一个线程以内,这样就无需同步也能保证线程之间不出现数据争用问题。能够经过java.lang.TreadLocal类来实现线程本地存储的功能。

相关文章
相关标签/搜索