某件正确的事情最终会发生, 活跃性问题包括死锁、饥饿、活锁
java
CPU将更多的时间花在线程调度而不是线程运行上
程序员
编写线程安全的代码,核心在于要对状态访问操做进行管理,特别是对共享的和可变状态的访问算法
共享意味着能够有多个线程访问
可变意味着变量的值在其生命周期内能够发生变化数据库
Java主要的同步机制包括synchronized关键字,volatile变量,显示锁以及原子变量编程
若是多个线程访问同一个可变的变量,且没有合适的同步,那么就会出现错误 修复手段: - 不在线程之间共享该变量 - 将状态变量修改成不可变 - 在访问状态变量时使用同步
设计线程安全的类时,良好的面向对象技术,不可修改性,以及明晰的不变性规范都能起到必定的帮助
彻底由线程安全类构成的程序不必定就是安全的,而在线程安全类中也能够包含非线程安全的类。api
线程安全性是一个在代码上使用的术语,但它只是与状态相关的,所以只能应用于封装其状态的整个代码,这可能时一个对象,也可能时整个程序
缓存
线程安全性的核心概念就是正确性。
正确性的含义时,某个类的行为与其规范彻底一致。
对于单线程,正确性近似定义为,所见即所知安全
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类时线程安全的
当多个线程访问某个类时,无论运行时采用何种调度方式,或者这些线程将如何交替执行,而且在主调代码中不须要额外的同步或者协同,这个类都表现出正确的行为,那么就称这个类时线程安全的。网络
无状态对象必定是线程安全的
多线程
++操做时一种紧凑的语法,但实际上操做并不是原子操做。因为不恰当的执行时序而出现不正确的结果是一种很是重要的状况——竞态条件(Race condition
最多见的竞态条件就是先检查后执行(Check Then Act), 即经过一个可能失效的观测结果来决定下一步的操做
@NotThreadSafe public class LazyInitRace{ private ExpensiveObject instance = null; public ExpensiveObject getInstance(){ if(instance = null){ instance = new ExpensiveObject(); } return instance; } }
竞态条件并不总会产生错误,还须要某种不恰当的执行顺序
原子操做是指,对于访问同一个状态的全部操做,这个操做是以原子方式执行的
要保持状态一致性,就须要在单个原子操做中更新全部相关的状态变量
java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)
同步代码块包括两部分
静态的synchronized方法以Class对象做为锁
synchronized(lock) { //访问或修改由锁保护的共享状态 }
每一个java对象均可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)
线程在进入同步代码块以前会自动得到锁,而且在推出代码块时自动释放锁,而不论时经过正常的控制路径退出,仍是经过从代码块中抛出异常退出。
得到内置锁的惟一途径就时进入由这个锁保护的同步代码块或方法。
重入意味着获取锁的操做的粒度时线程,而不是调用
这与pthread(POSIX线程)互斥体默认的枷锁行为不一样,pthread互斥体的获取操做时以调用为粒度
public class Widget{ public synchronized void doSomethind(){ } } public class LoggingWidget extends Widget{ public synchronized void doSomethind(){ System.out.println(this.toString() + ": classing doSomething"); super.doSomething(); } }
子类改写了父类的synchronized方法,若是没有可重入锁,那么这段代码将产生死锁。
因为Widget和LoggingWidget中doSomething方法都时synchronized方法,所以每一个doSomething方法在执行前都会获取Widget上的锁,若是锁时不可重入的,那么在调用super.doSomething时将永远没法得到Widget上的锁。
对于可能被多个线程同时访问的可变状态变量,在访问它时都须要持有同一个锁,在这种状况下,咱们常状态变量时由这个锁保护的
对象的内置锁与其状态之间没有内在的关联,虽然大多数类都将内置锁用做一种有效的加锁机制,但对象的域并不必定要经过内置锁来保护。当获取与对象关联的锁时,并不能阻止其余线程访问该对象,某个线程在得到对象的锁以后,并不能你组织其余线程访问该对象,某个线程在得到对象的锁以后,只能组织其余线程得到同一个锁。
每一个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道时哪个锁。
一种常见的额加锁约定是,将全部的可变状态都封装在对象内部,并经过对对象的内置锁对全部的可变状态的代码路径进行同步。
当类的不变性条件设计多个状态变量时,还有另外一个要求:不变性条件中的每一个变量必须由同一个锁来保护。所以能够在单个原子操做中访问或更新这些变量,从而确保不变性条件不被破坏。
两种不一样的同步机制不只会带来混乱,也不会在性能或安全性上带来任何好处。
当执行时间较长的计算或者可能没法快速我弄成的操做时(例如,网络I/O或控制台I/O),必定不要持有锁
在在多个线程执行读写操做时,咱们没法保证执行读操做的线程能适时的看到其余线程写入的值。
在没有同步的状况下,编译器、处理器以及运行时均可能对操做的执行顺序进行一些意想不到的调整。在缺少足够同步的多线程程序中,要想对内存操做的执行顺序进行判断,几乎没法得出正确结论。
非volatile类型的64位数值变量, Java内存模型要求变量的读取和写入都是原子操做。对于long和double,虚拟机容许将64位的读取和写入分为两个32位的操做。 当读取一个非volatile的long变量时,若是对这个变量的操做在不一样的线程中执行,那么极可能会出现错误
加锁的含义不只仅局限于互斥行为,还包括内存可见性,为了确保全部线程都能看到共享变量的最新值
volatile在代码中用来公职状态的可见性,一般比锁的代码更脆弱
volatile的正确用法包括:确保自身状态的可见性,确保他们所引用对象的状态的可见性,以及标识一些重要程序生命周期时间的发生
发布 一个对象的意思是指:时对象可以在当前做用域以外的代码中使用
如:将一个指向该对象的引用保存早其余代码能够访问的地方,或者在某一个非私有的方法中返回该引用。或者将引用传递到其余类的方法中。
发布内部状态可能会破坏封装性,并使得程序难以维持不变条件。
若是在对象构造完成以前就发布该对象,就会破坏线程安全性。当某个不该该发布的对象被发布时,这种状况就被称为逸出
假定有一个类C,对于C来讲,外部方法(Alien method)是指行为并不彻底由C来规定的方法,包括其它类中定义的方法以及类中能够被改写的方法
当把一个对象传递给某个外部方式的时候,就至关于发布了这个对象。
当某个对象逸出后,你必须假设由某个类或线程可能会无用该对象。这正是须要使用封装的最主要缘由:封装可以使得对程序的正确性进行分析变得可能。
public class ThisEscape{ public ThisEscape(EventSource source){ source.registerListener( new EventListener(){ public void onEvent(Event e){ doSomething(e); } }); } }
ThisEscape发布EventListener时,也隐含发布了ThisEscape,由于在这个内部类实例中包含了对ThisEscape实力的隐含引用。
不要在构造过程当中使this逸出
在构造过程当中使this引用逸出的常见错误是,在构造函数中启动一个线程。
若是想在构造函数中注册一个事件监听器或启动线程,那么可使用一个私有的构造函数和一个公共的工厂方法。
//使用工厂方法防止this引用在构造过程当中逸出 pubilc class SafeListener{ private final EventListener listener; private SafeListener(){ listener = new EventListener(){ public void onEvent(Event e){ doSomething(e); } } } public static SafeListener newInstance(EventSource source){ SafeListener safe = new SafeListener(); source.registerListener(sfae.listener); return sfae; } }
访问共享的可变数据时,一般须要使用同步。一种避免使用同步的方法就是不共享数据。
Thread Confinement
java语言中并无强制规定某个变量必须由锁保护,一样在java语言中也没法强制将对象封闭在某个线程中。
java语言提供了一些机制来帮助维持线程封闭性,例如局部变量ThreadLocal类
Ad-hoc线程封闭是指维护线程封闭性的职责彻底由程序实现来承担。
可是线程封闭技术的脆弱 程序中尽量少使用
局部变量的固有属性之一就是封闭在执行线程中。
位置对象引用的栈封闭性时,程序员须要多作一些工做已确保引用不会逸出
ThreadLocal类能使线程中的某个值与保存值的对象关联起来。每一个线程都存有一份独立的副本
ThreadLocal对象一般用于防止对可变的单实例变量或局部变量进行共享。
从概念上能够将ThreadLocal<T> 视为 Map<Thread,T>,其中保存了特定线程的值。 这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会做为垃圾回收
ThreadLocal的实现
对象不可变必定是线程安全的
知足如下条件对象才是不可变的
- 对象建立之后其状态不能修改 - 对象的全部域都是final类型 - 对象时正确建立的(在对象建立期间,this引用没有逸出)
在java内存模型中,final域还有着特殊的语义。final域能确保初始化过程的安全性,从而能够不受限制的访问不可变对象,并在共享这些对象时无需同步
经过将域声明为final类型,也至关于告诉维护人员这些域是不会变化的。
//可见性 致使的不安全发布 public Holder holder; public void initialize(){ holder = new Holder(42); }
即便某个对象的引用对其余线程是可见的,不意味着对象状态对于使用该对象的线程来讲必定是可见的。
任何线程均可以在不须要额外同步的状况下安全的访问不可变对象,即便在发布这些对象的时候没有使用同步
若是final类型的域所指向的对象是可变的,那么在访问这些域所指的对象的状态是仍须要同步
能够经过如下方式来安全的发布
= 将对象的引用保存早volatile类型或者AtomicReferance对象中
静态初始化器由JVM在类的初始化阶段执行。因为在JVM内部存在着同步机制,所以经过这种方式初始化的任何对象均可以被安全的发布
在没有额外的同步的状况下,任何线程均可以安全的使用被安全发布的事实不可变对象
不可变对象能够经过任意机制发布 事实不可变都西昂必须经过安全方式发布 可变对象必须经过安全方式来发布,而且必须是线程安全的或者由某个锁保护起来
包含三个基本要素
同步策略定义了如何在不违背对象不变性条件
许多状况下 全部与封装性是相互关联的:对象封装它拥有的状态。反之也成立,即它对它封装的状态拥有全部权。
容器类一般便显出一种全部权分离的状态, 其中容器类拥有自身的状态,而客户代码则拥有容器中各个对象的状态。
Instance confinement
当一个对象封闭到另外一个对象中是,可以访问被封装对象的路径都是可知的。
封闭机制更容易构造线程安全的类,由于当封闭类的状态时,分析类的线程安全性就无须检查整个程序
若是一个类是由多个独立且线程安全的状态变量组成,而且在全部的操做中都不包含无效状态转换,那么能够将线程安全性委托给底层的状态变量
若是一个状态变量是线程安全的,而且没有任何不变性条件来约束它的值,在变量的操做上也不存在任何不容许的状态转换,那么就能够安全的发布这个变量
同步容器类包括 Vector/ Hashtable 还包括JSK中添加的功能类似的类
他们实现线程安全的方式:将状态封装起来,每一个公有方法都进行同步,每次只有一个线程能访问容器的状态
并发操做修改容器时可能会出现意料以外的行为(迭代器操做)
能够经过客户端佳做来实现不可靠迭代问题,可是要牺牲掉一些伸缩性。
不但愿对迭代器加锁 ---解决办法 克隆容器, 缺点就是会存在显著的性能开销
编译器将字符串的链接操做转换为调用StringBuilder.append(Object)
正如封装对象的状态有助于维持不变性条件同样,封装对象的同步机制有助于确保实施同步策略
ConcurrentHashMap采用一种更细粒度的加锁机制——分段所(Locking Striping)
带来的结果就是并发访问环境下将实现更高的吞吐量,但线程中损失性能很是小。
ConcurrentHashMap与其余并发容器一块儿加强了同步容器类,它们提供的迭代器不会抛出ConcurrentModificationException,所以不须要对容器枷锁
返回的迭代器具备弱一致性,并不是及时失败,弱一致性的迭代器能够容忍并发的修改。
尽管有这些改进,但仍有一些须要权衡的因素,如size/isEmpty, 这些方法的语义被减弱,以反映容器的并发特性。
put if absent
remove if equal
在代码中Dion共用一个将抛出InterruptedException异常的方法时,你本身的方法也就变成了一个阻塞方法,
Thread.currentThread().interrupt();
能够用来确保某些活动知道其余活动都完成以后才执行
public long timeTasks(int nThreads, final Runnable task) thorw InterruptedException{ final CountDownLatch startGate = new CountDownLatch(); final CountDownLatch endGate = new CountDownLatch(); for (int i = i < nThreads; i++){ Thread t = new Thread(){ public void run(){ try{ startGate.await(); try{ task.run(); }finally { endGate.countDown(); } } catch(InterrupetedException ignored) {} } } t.start(); } long start = System.nanoTIme(); startGate.countDown(); endGate.await(); long end = System.nanoTime(); return end - start; }
FutureTask 表示的计算是经过Callable实现的至关于一种可生成结果的Runnable
FutureTask在Executor框架中表示异步任务
public class Preloader{ private final FutureTask<ProductInfo> future = new FutureTask<ProductInfo>(new Callable<ProductInfo>(){ public ProductInfo call() throws DataLoadException{ return loadProductInfo(); } }); private final Thread thread = new Thread(future); public void start(){ thread.start(); } public ProductInfo get() throws DataLoadException, InterruptedException{ try{ return future.get(); }catch(ExecutionException e){ Throwable cause = e.getCause(); if(cause instanceof DataLoadException) throw (DateLoadException) cause; else throw launderThrowable(cause); } } }
public class BoundedHashSet<T>{ private final Set<T> set; private final Semaphore sem; public BoundedHashSet(int bound){ this.get = Collections.synchronizedSet(new HasSet<T>()); sem = new Semaphore(bound); } public boolean add(T o) throws InterruptedException{ sem.acquire(); boolean wasAdded = false; try{ wasAdded = set.add(o); return wasAdded; } finally { if(!wasAdded) sem.release(); } } public boolean remove(Object o) { boolean wasRemoved = set.remove(o); if(wasRemoved) sem.release(); return wasRemoved; } }
闭锁时一次性对象,一旦进入终止状态就不能被重置Barried相似于闭锁,区别在于,全部线程必须同时到达栅栏位置才能继续执行。 闭锁用域等待时间,栅栏用域等他其余线程。
当线程到达栅栏位置时,调用await方法,这个方法将阻塞,直到全部线程都到达栅栏位置。若是全部线程都到达了栅栏位置,那么栅栏将打开。全部线程都被释放。此时栅栏被重置,以便下次使用。
若是对await调用超市,或者await阻塞的线程被中断,那么栅栏就被认为时打破了。全部阻塞的线程终止,并抛出BrokenBarrierException
CyclicBarrier还会是你将一个栅栏操做传递给构造函数,这是一个Runnable,当成功经过栅栏时,会执行它。
public class CellularAutomata{ private final Board mainBoard; private final CyclicBarrier barrier; private final Worker[] workers; public CellularAutomata(Board board){ this.mainBoard = board; int count = Runtime.getRuntime().availableProcessors(); this.barrier = new CyclicBarrier(count, new Runnable(){ public void run(){ mainBoard.commitNewValues(); } }); this.workers = new Worker[count]; for(int i = 0; i < count; i++){ workers[i] = new Worker(mainBoard.getSubBoard(count,i)); } } private class Worker implements Runnable{ private final Board board; public Worker(Board board){ this.board = board; } public void run(){ while (!board.hasConverged()){ for(int x = 0; x < board.getMaxX();x++){ for(int y = 0, y < board.getMaxY(); y++){ board.setNewValue(x,y,computeValue(x,y)); } } tyr{ barrier.await(); }catch(InterruptedException e){ return; }catck(BrokenBarrierException e){ return; } } } } public void start(){ for(int i = 0; i < workers.length;i++){ new Thread(worker[i]).start(); } mainBoard.waitForConvergence(); } }
#### 6.1.3 无限制建立线程的不足
= 稳定性
### 6.2 Executor框架
public interface Executor{ void execute(Runnable command); }
Executor是基于生产者消费者模式
线程池的优点
- 减少开销 = 提升响应 - 调整线程池的大小,能够建立足够多的线程以是处理器保持忙碌同时还避免了竞争
public interface ExecutorService extends Executor{ void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) thorws InterruptedException; }
运行中、关闭、终止
只有当大量相互独立且同构的任务能够并发处理时才能体现出将程序的工做负载分配到多个任务中带来的真正性能提高
行为良好的软件能很完善的处理失败、关闭和取消等过程
-在java中没有一种安全的抢占式方法来中止线程,所以也就没有安全的抢占式方法来中止任务。只有一些协做机制,使请求取消的任务和代码都遵循一种协商好的协议。
一般中断是实现取消最合理的方式
在java的api或语言规范中,并无将中断与任何取消语义关联起来,但实际上,若是在取消以外的其余操做使用中断,都是不合适的,而且很难支撑其更大的应用???
调用interrupt并不意味着当即中止目标线程正在进行的工做,而只是传递了请求中断的消息
每一个线程拥有各自的中断策略,所以除非你知道中断对该线程的含义,不然就不该该中断这线程
只有实现了线程中断策略的代码才能够屏蔽中断请求,在常规的任务和库代码中都不该该屏蔽中断请求
join 指定线程加入当前线程
yield 暂停当前线程
当Future.get抛出InterruptedException或TimeoutException时,若是你知道再也不须要结果,那么就能够调用Future.cancel来取消任务
并不是全部的可阻塞方法或者阻塞机制都能响应中断
例如 一个线程因为执行同步的socket I/O或者等待得到内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此以外并无其余任何做用。
对于那些因为执行不可中断操做而被阻塞的线程,可使用相似于中断的手段来中止这些线程,可是要求咱们必须知道线程阻塞的缘由。
shutdown正常关闭
shutdownNow 强行关闭
正常关闭的触发方式有多种
在正常关闭中,JVM首相调用全部已注册的关闭钩子(Shutdown Hook)
关闭钩子是指经过Runtime.addShutdownHook注册的但还没有开始的线程,能够用于实现服务或者应用程序的清理工做。
public void start(){ Runtime.getRuntime().addShutdownHook(new Thread(){ public void run(){ try{ LogService.this.stop(); }catch(InterruptedException ingnore){} } }); }
Daemon Thread
除了主线程意外,全部的线程都是守护线程(垃圾回收线程和其余的辅助线程)
普通线程和守护线程的区别在于,当一个线程退出时,JVM会检查其余正在运行的线程,若是这些线程都是守护线程,那么jvm会正常退出操做。
当JVM中止时,全部仍然存在的守护线程将被抛弃,既不会执行finally也不会执行回卷栈,而jvm只是直接退出
finalize
避免使用终结器
Runtime.availableProcessors
观察CPU利用水平来设置线程数
N_cpu = number of CPUs U_cpu = target CPU utilization, 0 <= U_cpu <= 1 W/C = ratio of wait time to compute time N_threads = N_cpu * U_cpu *(1+W/C)
CPU并非惟一影响线程大小的资源,还报错内存,文件句柄,套接字句柄和数据库连接等
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<RUnnable> workQueue, ThreadFactory threadFactory, RejectExecutionHandler handler){……}
每当线程池须要建立一个线程时,都是经过一个县城工厂方法来完成的。
默认的线程工厂方法将建立一个新的,非守护的线程,而且不包含特殊的配置信息。
若是全部线程以固定的顺序来得到锁,那么在程序中就不会出现锁顺序死锁问题
查看是否存在嵌套的锁获取操做
在调用某个方法是不须要持有锁,那么这种调用被称为开放调用
经过开放调用来避免死锁,相似于采用封装机制提供线程安全的方法
活锁一般发生在处理事务消息的应用程序中,若是不能成功地处理某个消息,那么消息处理机制将会回滚整个事务,并将它从新放在队列的开头。会致使重复调用,虽然县城并无阻塞,可是也没法继续进行下去。
一般是因为过分的错误恢复代码形成的,由于错误的将不可修复的错误做为可修复的错误
当多个相互协做的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都没法继续执行时,就发生了活锁。解决这种问题,能够引入随机性(Raft 中的随机性)
可伸缩性指的是,增长计算资源是,程序的吞吐量或者处理能力响应增长
原子变量、读写锁
ReentrantLock
并非一种替代内置加锁的方法,而时当内置加锁机制不适用时,做为一种可选择的高级功能。
Lock提供了一种无条件的,可轮询的、定时的以及可中断的锁获取操做
Lock的视线中必须提供与内部锁象通的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能方面有所不一样
public interface Lock{ void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long timeOut, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
为何要建立一个与内置锁如此类似的加锁机制? 大可能是状况下,内置锁均可以知足工做,可是功能上存在局限性,例如,没法中断一个正在等待获取锁的线程,没法在请求获取一个锁时,无限等待下去。内置锁必须在获取该锁的代码块中释放,这简化了编码工做,而且与异常处理操做实现了很好的交互,但却没法实现非阻塞结构的加锁规则。
Lock lock = new ReentrantLock(); .... lock.lock(); try{ //doSomethind() }finally{ lock.unlock(); }
定时锁与轮询所提供了避免死锁发生的另外一种选择
public boolean sendOnSharedLine(String message) throws InterruptedException{ lock.lockInterruptibly(); try{ return cancellableSendOnSharedLine(message); }finally{ lock.unlock(); } } private boolean cancellableSendOnSharedLine(String message) throws InterruptedException{ ... }
公平性将因为挂起线程和回复线程时存在的开销极大下降性能。在实际状况中,统计上的公平性保证——确保被阻塞的线程最终得到锁,一般已经够用
与显示锁相比,内置锁仍然具备很大的优点。内置锁为许多开发人员锁熟悉,而且简洁紧凑。 在一些内置锁没法知足需求的状况下 ReentrantLcok能够做为一种高级工具。当须要一些高级功能是才应该使用ReentrantLock,这些功能包括可定时的,可轮询的与可中断的锁获取操做,公平队列以及非块结构的锁,不然,仍是应该优先使用synchronized 另外synchronized在线程转储中能给出在那些调用帧中得到了哪些锁,并可以检测和识别发生死锁的线程。 synchronized时JVM的内置属性,它能执行一些优化,例如对线程封闭的锁对象的锁消除优化,经过增长锁的粒度来消除内置锁的同步。
public interface ReadWriteLock{ Lock readLock(); Lock writeLock(); }
读写锁中的一些可选实现
Object.wait会自动释放锁,并请求操做系统挂起当前线程
@ThreadSafe public class BoundedBuffer<V> extends BaseBoundedBuffer<V>{ //条件谓词: not-full(!isFull()) //条件谓词: not-emptu(!isEmpty()) public BoundedBuffer(int size){ super(size); } public sunchronized void put(V v) throws InterruptedException{ while(isFull()){ wait(); } doPut(v); notifyAll(); } public synchronized V take() throws InterruptedException{ while(isEmpte()){ wait(); } V v = doTake(); notifyAll(); return v; } }
条件谓词:使某个操做成为状态依赖操做的前提
在有界缓存中,只有当缓存不为空take方法才能执行, 对于take来讲,他的条件谓词就是缓存不为空
在条件等待中存在一种重要的三元关系, 包括加锁,wait方法和一个条件谓词
每一次wait调用都会隐式的与特定的条件谓词关联起来,当调用某个特定的条件谓词的wait时,调用者必须已经持有与条件队列相关的锁,而且这个锁必须保护着构成条件谓词的状态变量
wait方法的返回并不必定意味着线程正在等待的条件谓词成真了
内置条件队列能够与多个条件谓词一块儿使用。
使用条件等待时 如 Object.wait或者 Condition.wait - 一般都有一个条件谓词——包括一些对象状态的测试,线程在执行前必须首先经过这些测试 - 在调用wait以前测试条件谓词,而且从wait中返回时再次进行测试 - 在一个循环中调用wait - 确保使用与条件队列相关的锁来保护构成条件谓词的哥哥状态变量 - 当条用wait、notify、nitofyAll时必定要持有与条件队列相关的锁 - 在检查条件谓词以后以及开始执行响应的操做以前,不要释放锁
丢失信号是指:线程必须等待一个已经为真的条件,可是在开始等待以前没有检查条件谓词
因为在调用notify或nitofyAll时必须持有条件队列对象的锁,而若是这些等待中的线程此时不能从新得到锁,那么没法从wait返回,所以发出通知的线程应该尽快释放锁,从而确保正在等待的线程尽量快的解除阻塞
只有同时知足如下两个条件时,才能用单一的nofify而不是notifyAll - 全部等待线程的类型都象通 - 单进单出
Condition是一种广义的内置条件队列
public interface Condition{ void await() throws InterruptedException; boolean await(long timeOut, TimeUnit unit) throws InterruptedException; long awaitNanos(long nanosTimeout) throws InterruptedException; void awaitUniterruptibly(); boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); void signalAll(); }
Lock.newCondition
@TreadSafe public class ConditionBoundedBuffer<T>{ protected final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); @GuardBy("lock") private final T[] items =(T[]) new Object[BUFFER_SIZE]; @GuardBy("lock") private int tail, head, count; public void put(T x) throw InterruptedException{ lock.lock(); try{ while (count == items.length){ notFull.await(); } items[tail] = x; if(++tail == items.length){ tail = 0; } ++count; notEmpty.signal(); }finally{ lock.unlock(); } } public T take() throws InterruptedException{ lock.lock(); try{ while(count == 0){ notEmpty.await(); } T x = items[head] items[head] == null; if(++head == items.length){ head = 0; } --count; notFull.signal(); return x; } finally{ lock.unlock(); } } }
经过将两个条件谓词分别放到两个等待线程集中
实际上 ReentrantLock和 Semaphore的实现都使用了一个共同的基类,即AbstractQueuedSynchronizer(AQS)
AQS是一个用域构建锁和同步器的框架,许多同步器均可以经过AQS构造
//AQS中获取操做和释放操做的基标准形式 boolean acquire() throws InterruptedException{ while(当前状态不容许获取操做){ if(须要阻塞获取请求){ 若是当前线程不在队列中,则将其插入队列 阻塞当前线程 } else 返回失败 } 可能更新同步器的状态 若是线程位于队列中,则将其移出队列 返回成功 } void release(){ 更新同步器的状态 if(新的状态容许某个被阻塞的线程获取成功) 解除队列中一个或多个线程的阻塞状态 }
CAS的主要缺点是, 它将使调用者处理竞争问题,而锁能自动处理竞争问题
java.util.concurrent中的大多数类在实现时直接或间接的使用了这些原子变量类
基于锁的算法可能会发生各类活跃性故障,若是线程在持有锁时因为阻塞I/O,内存也确实或其余延迟致使推迟执行,那么颇有可能其余线程都不能继续执行。
若是在某种算法中,一个线程的失败或挂起不会致使其余线程也失败或挂起,那么这种算法就被称为非阻塞算法。
非阻塞算法一般不会出现死锁和优先级反转的问题,可是可能会出现饥饿和活锁问题。
@ThreadSafe public class ConcurrentStack <E> { AtomicReference<Node<E>> top = new AtomicReference<Node<E>>(); public void push(E item){ Node<E> new Head = new Node<E>(item); Node<E> oldHead; do{ oldHead = top.get(); newHead.next = oleHead; } while (!top.compareAndSet(old,newHead)); } public E pop(){ Node<E> oldHead; Node<E> newHead; do{ oldHead = top.get(); if(oldHead == null){ return null; } newHead = oldHead.next; } while(!top.compareAndSet(oldHead, newHead)); } private static Node<E>{ public final E item; public Node<E> next; public Node(E item){ this.item = item; } } }
Java语言规范要求JVM在线程中维护一种相似串行的语义:只要程序的最终结果与在严格串行的环境中执行结果相同,那么上述全部操做都是容许的。
JMM规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操做再合适将对于其余线程可见。
java内存模型时经过各类操做来定义的,包括对变量的读/写操做,监视器的加锁和释放惭怍,以及线程的启动和合并操做,JMM位程序中全部的操做定义了一个偏序关系
称之为Happens-before
当一个变量被多线程读取,而且至少被一个线程写入时,若是在取操做和写操做之间没有依照Happens-before来排序,那么就会产生数据竞争问题。
见jvm笔记
除了不可变对象意外,使用被另外一个线程初始化的对象一般都是不安全的,除非对象的发布操做是在使用该对象的线程开始使用以前执行。
初始化安全性将确保对于被正确构造的对象,全部线程都能看到有构造函数位对象给各个final域设置的正确值,而无论采用何种方式来发布对象。并且,对于能够经过被正确构造对象中某个final域到达的任意变量,将一样对于其余线程可见。
对于含有final域的对象,初始化安全性能够防止对象的初始引用被重排序到构造过程以前。当构造函数完成时,构造函数对final域的全部写入操做,以及对经过这些域能够到达的任何变量的写入操做,都将被冻结。而且任何得到该对象引用的线程至少能确保看到被冻结的值。
初始化安全性只能保证经过final域可达的值从构造过程完成时开始的可见性。对于经过非final域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性。