java线程的sleep(),wait(),notify(),yield();
1.sleep()使线程休眠一段时间,一段时间结束后,线程进入可执行状态,但并不是立即执行,只是在被排程器调用的时候才执行。在休眠期间,并不释放所持有的“锁”;
2.wait()使线程休眠一段时间,若设置参数,时间到时,线程就自动进入可执行状态。若没有,则需要notify()方法去调用。注意:wait()方法和notify()方法都时针对this对象的,调用wait()方法后,会释放加在对象上的“锁”。
3.yield()使线程放弃执行的权利,进入可执行状态,也就意味着线程在yield()方法后,有可能又执行。使用yield()方法,线程并不释放自己锁持有的“锁”。
1 sychronized 方法
- 把方法标记为synchronized : 一旦某个线程处于一个标记为synchronized的方法中, 那么这个线程从该方法返回之前,其它要调用类中任何标记为synchronized的方法的线程都会被阻塞。相当于 sychronized ( this )
2 sychronized 同步块
- "同步控制块"(synchronized block),在进入此段代码前, 必须得到syncObject对象的锁.如果其它线程已经得到这个锁,那么就得等到锁被释放以后,才能进入临界区.
1. java中的每个对象都有一个锁,当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法在去访问该syncronized 方法了,直到之前的那个线程执行方法完毕后,其他线程才有可能去访问该synchronized方法。
2.如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到某个synchronzed方法,那么在该方法没有执行完毕前,其他线程无法访问该对象的任何synchronzied 方法的,但可以访问非synchronzied方法。
3.如果synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchuronized方法所在对象的对应的Class对象,
因为java中无论一个类有多少个对象,这些对象会对应唯一一个Class 对象,因此当线程分别访问同一个类的两个对象的static,synchronized方法时,他们的执行也是按顺序来的,也就是说一个线程先执行,一个线程后执行。
4 volatile 只能保证变量可见性,不能保证操作原子性,是轻量级同步方案,一般用在变量赋值操作。synchronized都可以。volatile还能保证变量不乱序。
volatile boolean asleep;
if(!asleep){
dosth();
}
5 threadlocal可以保证线程安全,不必把变量当做参数传递
6 synchronizedSet系列接口可以解决部分同步问题,但是在遍历上必须加同步(itrator会出现同步问题,容器的toString也会调用itrator,一般是加锁或者复制来解决),性能也低,一般被concurrent包下类取代(新的并非容器的遍历没有同步问题),大部分非concurrent下的类在迭代都会有线程安全问题。
synchronizedSet
public static <T> Set<T> synchronizedSet(Set<T> s)返回指定 set 支持的同步(线程安全的)set。为了保证按顺序访问,必须通过返回的 set 完成对所有底层实现 set 的访问。
在返回的 set 上进行迭代时,用户必须手工在返回的 set 上进行同步:
Set s = Collections.synchronizedSet(new HashSet());
...
synchronized(s) {
Iterator i = s.iterator(); // Must be in the synchronized block
while (i.hasNext())
foo(i.next());
}
不遵从此建议将导致无法确定的行为。
7 CopyOnWriteArrayList和 CopyOnWriteSet 是ArrayList
和set 的线程安全的变体。通常适合在迭代情况多,改动情况小,并且数量小的情况下用。迭代器保证不会抛出ConcurrentModificationException但是不支持修改
8 如果一个线程启动一个很大开销,另外一个线程不知道,可能会重复这个动作,这个时候尽量保证另外的线程知道其他线程已经在做,自己只要等待结果就可以了。下面例子主要是避免多线程的相同计算,map里取出的是动作而不是值。高速缓存就是避免计算相同的值。FutureTask 代表一个计算结果,或者已经结束,或者正在运行(可取消的异步计算。利用开始和取消计算的方法、查询计算是否完成的方法和获取计算结果的方法,此类提供了对 Future 的基本实现。仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。),其他的方法无法区分动作是否完成。
public class FutureTaskTest {
private ComputeProcessor p;
private ConcurrentHashMap<Object, Future<Integer>> cache = new ConcurrentHashMap<Object, Future<Integer>>();
public FutureTaskTest(ComputeProcessor p){
this.p = p;
}
public int getFutureResult(final int a,final int b) throws Exception{
while(true) -----------------------------------这个加true
Future temp = cache.get("key");
if(temp ==null){
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return p.computer(a, b);
}
});
cache.putIfAbsent("key", task);
task.run();
temp = task;
}
try {
return ((Integer) temp.get()).intValue();
} catch (CancelException e) {
cache.remove("key");-----------------------------如果取消计算,删除缓存futuretask
}cathce(EcxutionException ex){throw new Exception();}----------------------------跳出循环看情况清缓存
}} 缓存future而不是一个值会带来缓存污染问题,如果一个计算被取消或者错误,其他对其访问也会变成取消或者错误,所以出现异常要删除缓 存,防止再次重现}
9 CountDownLatch和CyclicBarrier 区别在于 CountDownLatch里的线程是到了运行的目标后继续干自己的其他事情,而CyclicBarrier 需要等待其他线程后才能继续完成下面的工作
下面来详细描述下CountDownLatch的应用场景:
例如:百米赛跑:8名运动员同时起跑,由于速度的快慢,肯定有会出现先到终点和晚到终点的情况,而终点有个统计成绩的仪器,当所有选手到达终点时,它会统计所有人的成绩并进行排序,然后把结果发送到汇报成绩的系统。
其实这就是一个CountDownLatch的应用场景:一个线程或多个线程等待其他线程运行达到某一目标后进行自己的下一步工作,而被等待的“其他线程”达到这个目标后继续自己下面的任务。
这个场景中:
1. 被等待的“其他线程”------>8名运动员
2. 等待“其他线程”的这个线程------>终点统计成绩的仪器
那么,如何来通过CountDownLatch来实现上述场景的线程控制和调度呢?
jdk中CountDownLatch类有一个常用的构造方法:CountDownLatch(int count);
两个常用的方法:await()和countdown()
其中count是一个计数器中的初始化数字,比如初始化的数字是2,当一个线程里调用了countdown(),则这个计数器就减一,当线程调用了await(),则这个线程就等待这个计数器变为0,当这个计数器变为0时,这个线程继续自己下面的工作。下面是上述CountDownLatch场景的实现:
Work类(运动员):
import java.util.concurrent.CountDownLatch;
public class Work implements Runnable {
private int id;
private CountDownLatch beginSignal;
private CountDownLatch endSignal;
public Work(int id, CountDownLatch begin, CountDownLatch end) {
this.id = id;
this.beginSignal = begin;
this.endSignal = end;
}
@Override
public void run() {
try {
beginSignal.await();
System.out.println("起跑...");
System.out.println("work" + id + "到达终点");
endSignal.countDown();
System.out.println("work" + id + "继续干其他事情");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Main类(终点统计仪器):
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) {
CountDownLatch begSignal = new CountDownLatch(1);
CountDownLatch endSignal = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
new Thread(new Work(i, begSignal, endSignal)).start();
}
try {
begSignal.countDown(); //统一起跑
endSignal.await(); //等待运动员到达终点
System.out.println("结果发送到汇报成绩的系统");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
下面详细描述下CyclicBarrier的应用场景:
有四个游戏玩家玩游戏,游戏有三个关卡,每个关卡必须要所有玩家都到达后才能允许通关。
其实这个场景里的玩家中如果有玩家A先到了关卡1,他必须等待其他所有玩家都到达关卡1时才能通过,也就是说线程之间需要互相等待,这和CountDownLatch的应用场景有区别,CountDownLatch里的线程是到了运行的目标后继续干自己的其他事情,而这里的线程需要等待其他线程后才能继续完成下面的工作。
jdk中CyclicBarrier类有两个常用的构造方法:
1. CyclicBarrier(int parties)
这里的parties也是一个计数器,例如,初始化时parties里的计数是3,于是拥有该CyclicBarrier对象的线程当parties的计数为3时就唤醒,注:这里parties里的计数在运行时当调用CyclicBarrier:await()时,计数就加1,一直加到初始的值
2. CyclicBarrier(int parties, Runnable barrierAction)
这里的parties与上一个构造方法的解释是一样的,这里需要解释的是第二个入参(Runnable barrierAction),这个参数是一个实现Runnable接口的类的对象,也就是说当parties加到初始值时就出发barrierAction的内容。
下面来实现上述的应用场景:
Player类(玩家类)
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Player implements Runnable {
private CyclicBarrier cyclicBarrier;
private int id;
public Player(int id, CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
this.id = id;
}
@Override
public void run() {
try {
System.out.println("玩家" + id + "正在玩第一关...");
cyclicBarrier.await();
System.out.println("玩家" + id + "进入第二关...");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
GameBarrier类(关卡类,这里控制玩家必须全部到达第一关结束的关口才能进入第二关)
import java.util.concurrent.CyclicBarrier;
public class GameBarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
@Override
public void run() {
System.out.println("所有玩家进入第二关!");
}
});
for (int i = 0; i < 4; i++) {
new Thread(new Player(i, cyclicBarrier)).start();
}
}
}
10 线程正常中断
a Executors 线程池是回捕捉非正常异常导致中断,线程池会自动生成新线程弥补已经消失的线程。但是单个Thread不会,一定加异常去捕获,否则会线程泄露。也可以用UncaughtExceptionHandler来统一捕获线程异常。
Thread的run方法是不抛出任何检查型异常(checked exception)的,但是它自身却可能因为一个异常而被终止,导致这个线程的终结。最麻烦的是,在线程中抛出的异常即使在主线程中使用try...catch也无法截获,因此可能导致一些问题出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。 主线程之所以不处理子线程抛出的RuntimeException,是因为线程是异步的,子线程没结束,主线程可能已经结束了。UncaughtExceptionHandler名字意味着处理未捕获的异常。更明确的说,它处理未捕获的运行时异常。Java编译器要求处理所有非运行时异常,否则程序不能编译通过。这里“处理”的是方法里throws子句声明的异常或在try-catch块里的catch子句的异常。
- package demo;
- import java.lang.Thread.UncaughtExceptionHandler;
- public class ThreadTest
- {
- public static void main(String[] args)
- {
- ThreadA a = null;
- try
- {
- ErrHandler handle = new ErrHandler();
- a = new ThreadA();
- a.setUncaughtExceptionHandler(handle);// 加入定义的ErrHandler
- a.start(); // 线程的run抛出的RuntimeException异常无法抓到
- // a.run(); 普通方法抛出RuntimeException异常可以抓到
- }
- catch (Exception e)
- {
- System.out.println("catch RunTimeException a"); // 不起作用,但是Exception已经交给handle处理
- }
- // 普通线程即使使用try...catch也无法捕获到抛出的异常
- try
- {
- ThreadB b = new ThreadB();
- b.start();
- }
- catch (Exception e)
- {
- System.out.println("catch RunTimeException b"); // 不起作用
- }
- }
- }
- /**
- * 自定义的一个UncaughtExceptionHandler
- */
- class ErrHandler implements UncaughtExceptionHandler
- {
- /**
- * 这里可以做任何针对异常的处理,比如记录日志,重启线程,启动诊断,关闭程序等等
- */
- public void uncaughtException(Thread a, Throwable e)
- {
- System.out.println("This is:" + a.getName() + ",Message:" + e.getMessage());
- e.printStackTrace();
- }
- }
- /**
- * 拥有UncaughtExceptionHandler的线程
- */
- class ThreadA extends Thread
- {
- public ThreadA()
- {
- }
- public void run()
- {
- double i = 12 / 0;// 抛出ArithmeticException的RuntimeException型异常
- }
- }
- /**
- * 普通线程
- */
- class ThreadB extends Thread
- {
- public ThreadB()
- {
- }
- public void run()
- {
- try
- {
- double i = 12 / 0;// 抛出ArithmeticException的RuntimeException型异常
- }
- catch (RuntimeException e)
- {
- throw e;
- }
- }
- }
可以通过构造ThreadFactory 给线程池加上捕获异常的UncaughtExceptionHandler ,就可以对线程池的线程捕获异常
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler)
只有通过execut提交的任务,才能将它抛出的异常给未捕获异常处理器,而通过submit提交的任务,无论是否是未检查的都将视为返回状态一部分,通常被future.get重新抛出
b 生产者线程和消费者线程最好同时关闭,简单方法就是用线程池,直接关闭线程池,但是shuntdownnow 有局限性,它能获得未执行的线程列队,但是已经执行还没执行完的线程无法获知,可以修改组件来获得。
static class TraceThread extends AbstractExecutorService{
private ExecutorService exe;// 其他方法通过它来代理
private Set<Runnable> jobList = new HashSet<Runnable>();
public Set<Runnable> getJobList() throws Exception{
if(!exe.isTerminated()){
throw new Exception("线程还未结束");
}
return jobList;
}
@Override
public void shutdown() {
// TODO Auto-generated method stub
}
@Override
public List<Runnable> shutdownNow() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isShutdown() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isTerminated() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
// TODO Auto-generated method stub
return false;
}
@Override
public void execute(final Runnable command) {
exe.execute(new Runnable(){
@Override
public void run() {
try{
command.run();
}finally{
if(Thread.currentThread().isInterrupted()&&isShutdown()){
jobList.add(command);
}
}
}
});
}
}
c 私有的executor 简化单次操作,并且有完整的生命周期
private static boolean checkEmail(Set<String> hosts)throws Exception{
final AtomicBoolean result = new AtomicBoolean(false);
ExecutorService es =Executors.newCachedThreadPool();
try{
for(final String host:hosts){
es.submit(new Runnable(){
public void run() {
if(checkEmail(host)){
result.set(true);
}
}
});
}}finally{
es.shutdown();
es.awaitTermination(3, TimeUnit.SECONDS);//这里是阻塞关闭动作,等待关闭动作完成。
}
return result.get();
}
系统必须处理中断来增加代码强制性
public static void processJobs(Runnable job,long timeout,TimeUnit unit){
ExecutorService ex = Executors.newCachedThreadPool();
Future f = ex.submit(job);
try{
f.get(timeout,unit);
}catch(Exception e){
e.printStackTrace();
}finally{
//如果已经结束没影响
f.cancel(true);
}
}
中断的几种形式
1 共享变量 volatile boolean flag (如果已经阻塞如blockqueue.get,没法进行中断)
2 interrputed (针对java内置阻塞,和 Thread.currentThread.isInterrupted 一起进行判断,跳出循环)
3 future.cancel
传递中断2种途径
1 传递中断异常
2 不能抛出异常的传递中断状态
public class ThreadDemo extends Thread{
public static void main(String[] args) throws InterruptedException {
Thread thread = new ThreadDemo();
thread.start();
thread.sleep(100);
thread.interrupt(); //中断线程
}
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
System.out.println("thread running");
try {
Thread.sleep(1000);
}catch(InterruptedException e)
{
System.out.println("~~~~~~1~~~~~~~"+Thread.currentThread().isInterrupted()); false
System.out.println("InterruptedException");
Thread.currentThread().interrupt();
System.out.println("~~~~~~2~~~~~~~"+Thread.currentThread().isInterrupted()); true
}
}
System.out.println("thread interrupted");
}
}
注意几点: 1 线程如果退出后再去取中断状态,一直是false
2 线程在阻塞时遇到中断,JVM的做法是,先设置线程的状态为中断,然后再阻塞语句处直接抛出一个InterruptedException异常,然后再清除刚才设置的中断状态。所以上述代码 1处是false,再还原中断现场后2处就为true了
3 如果本线程还原中断状态,其他线程取这个线程的中断状态,也不一定能取到正确的值,如下2段代码
getNextTask方法是个while循环,有返回值,这里返回数据并不表示queue.take()所在的线程结束,同时在返回之前保存线程中断现场,所以,外界是可以拿到中断状态的。
第二个用例如果要在1处拿到finally里保存中断状态,必须保证目标线程在取状态前还在,还有当前线程执行t.isInterrputed时候,目标线程已经保存了中断状态,这两者都不满足,所以无法获取正确的中断状态
public Task getNextTask(BlockingQueue<Task> queue) {
boolean interrupted = false;
try {
while (true) {
try {
return queue.take();
} catch (InterruptedException e) {
interrupted = true;
// fall through and retry
}
}
} finally {
//在返回前恢复中断状态,不要掩盖中断状态
if (interrupted)
Thread.currentThread().interrupt();
}
}
public class TestExecutor {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
inner1 inner =new inner1();
Thread t =new Thread(inner);
t.start();
Thread.currentThread().sleep(2000);
t.interrupt();
System.out.println("~~~~~~1~~~~~~~~"+t.isInterrupted());
}
static class inner1 implements Runnable{
@Override
public void run() {
try {
Thread.currentThread().sleep(8000);
} catch (InterruptedException e) {
System.out.println("~~~~~2~~~~~"+Thread.currentThread().isInterrupted());
e.printStackTrace();
}finally{
Thread.currentThread().interrupt();
}
}
}
}
12 线程池扩展
a 线程池最好只为一个种类任务服务,不同种类的任务为了提高利用率最好分开线程池。线程池线程个数设定不仅仅受cpu影响,还受制IO,内存等多种因素,大致可以由
下面公式推算:
线程个数 = cpu数量 * cpu使用率 *(1+w/c)
w/c等待时间和计算时间的比率
b newFixedPool newSingleThreadExecutor默认使用无限的linkedBlockingQueue
newSingleThreadExecutor 与 newFixedThreadPool(1) 区别
((ThreadPoolExecutor)newFixedThreadPool(1)).setCorePoolSize(3); 即newFixedThreadPool(1)可以后期修改线程数,不能保证线程只有一个。 而 newSingleThreadExecutor可以保证。
c 用户可以在后期对线程池属性重新定义,但不建议随意修改,用户可以用unconfigExcutorService包装禁止修改属性
ExecutorService exec = Excutors.newCachedThreadPool();
if(exec instanceof ThreadPoolExecutor)
((ThreadPoolExecutor)exec).setCorePoolSize(10); //会执行
else
thrown new Exception("bad operation");
ExecutorService exec = Executors.unconfigurableExecutorService(Executors.newCachedThreadPool());
if(exec instanceof ThreadPoolExecutor){
((ThreadPoolExecutor)exec).setCorePoolSize(10); //不会执行,这里ExecutorService 已经被包装过
}
d 用户可以扩展threadPoolExcutor 覆盖beforeExecute,afterExecutor 添加诸如计时,log等扩展功能
13 减少对锁的申请
a 减少持有锁时间 如synchronized修饰方法=》synchronized块
b 减少请求锁频率
c 协调机制取代锁机制
d 开放式调用
减小锁的力度主要表现在分离锁,分拆锁.如果操作是完全独立的,锁也应该完全独立就像两个不同的业务分别用两个不同的线程安全set保存,其实就是隐式分拆锁
分拆锁
public class Server{
public final Set<String> users;
public final set<String> queries;
public synchronized void addUser(String n){
users.add(n);
}
public synchronized void addQuery(String n){
queries.add(n);
}
}
public class Server{
public final Set<String> users;
public final set<String> queries;
public void addUser(String n){
synchronized (users){
users.add(n);
}
}
public synchronized void addQuery(String n){
synchronized (queries){
queries.add(n);
}
}
}
分离锁
把一个竞争激烈的锁分拆成多个,减少对锁请求几率。例如concurrentHashMap
14 Lock
synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断 (这里是等待中断,而不是执行中断)
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候
public class LockBox {
private Object lock=new Object();
private Lock reentlock = new ReentrantLock();
public void write(){
synchronized(lock){
System.out.println("write coming");
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("write finish");
}
}
public void read(){
synchronized(lock){
System.out.println("read coming");
System.out.println("read finish");
}
}
public void write2(){
try{
reentlock.lock();
System.out.println("write coming");
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("write finish");
}finally{
reentlock.unlock();
}
}
public void read2() throws InterruptedException{
reentlock.lockInterruptibly();
try{
System.out.println("read coming");
System.out.println("read finish");
}finally{
reentlock.unlock();
}
}
}
public class TestLockInterception {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
LockBox box = new LockBox();
read r = new read(box);
write w = new write(box);
Thread t1 = new Thread(r);
Thread t2 = new Thread(w);
t2.start();
t1.start();
Thread.currentThread().sleep(3000);
t1.interrupt();
}
static class read implements Runnable{
private LockBox box;
public read(LockBox box){
this.box=box;
}
@Override
public void run() {
try {
box.read2();
} catch (InterruptedException e) {
System.out.println("读操作相应中断");
e.printStackTrace();
}
}
}
static class write implements Runnable{
private LockBox box;
public write(LockBox box){
this.box=box;
}
@Override
public void run() {
box.write2();
}
}
}
lock condition 实现消费者和生产者
public class TestComsumer {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
LockBox box = new LockBox();
Lock reentlock = new ReentrantLock();
Condition product = reentlock.newCondition();
Condition consume = reentlock.newCondition();
ProductThread pt = new ProductThread(box,reentlock,product,consume);
ComsumeThread ct = new ComsumeThread(box,reentlock,product,consume);
Thread t1 = new Thread(pt);
Thread t2 = new Thread(ct);
t2.start();
t1.start();
}
static class LockBox{
private boolean flag=false;
public boolean hasFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
static class ProductThread implements Runnable{
private LockBox box;
private Condition product;
private Condition consume;
private Lock lock;
public ProductThread(LockBox box,Lock lock,Condition product,Condition consume){
this.box=box;
this.lock=lock;
this.product=product;
this.consume=consume;
}
@Override
public void run() {
for(int i=1;i<=100;i++){
lock.lock();
try{
if(box.hasFlag()){
try {
product.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("~~~~~~~生产鸡蛋~~"+i);
box.setFlag(true);
consume.signal();
}finally{
lock.unlock();
}
}
}
}
static class ComsumeThread implements Runnable{
private LockBox box;
private Condition product;
private Condition consume;
private Lock lock;
public ComsumeThread(LockBox box,Lock lock,Condition product,Condition consume){
this.box=box;
this.lock=lock;
this.product=product;
this.consume=consume;
}
@Override
public void run() {
for(int i=1;i<=100;i++){
lock.lock();
try{
if(!box.hasFlag()){
try {
consume.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
}
System.out.println("~~~~~~~取鸡蛋~~"+i);
box.setFlag(false);
product.signal();
}finally{
lock.unlock();
}
}
}
}
}
1 ReadWritelock
读写锁,写-写 排斥,读-写 排斥,读-读 不排斥,系统默认是写锁优先级高,如果时间间隔差不多,始终写锁占有
用户还可以用读写锁自行封装hashmap使之变成线程安全的,因为lock和synchronized块语义相同,因此每次锁定是整个集合,性能较低
public class TestReadWriteLock {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
Account account = new Account();
ReadWriteLock lock = new ReentrantReadWriteLock();
es.execute(new Son(lock,account));
es.execute(new Father(lock,account));
es.execute(new Mother(lock,account));
}
static class Account{
private long amount=10000;
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;
}
public void fetchMoney(){
amount=amount-100;
}
public boolean hasMoney(){
return amount>0;
}
}
static class Son implements Runnable{
private ReadWriteLock lock;
private Account account;
public Son(ReadWriteLock lock,Account account){
this.lock=lock;
this.account=account;
}
@Override
public void run() {
while(true&&account.hasMoney()){
lock.writeLock().lock();
try{
account.fetchMoney();
System.out.println("~~~~~~~~取款100还剩 "+account.getAmount());
}finally{
lock.writeLock().unlock();
}
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Father implements Runnable{
private ReadWriteLock lock;
private Account account;
public Father(ReadWriteLock lock,Account account){
this.lock=lock;
this.account=account;
}
@Override
public void run() {
while(true&&account.hasMoney()){
lock.readLock().lock();
try{
System.out.println("~~~~~~~~父亲查看余额 "+account.getAmount());
}finally{
lock.readLock().unlock();
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Mother implements Runnable{
private ReadWriteLock lock;
private Account account;
public Mother(ReadWriteLock lock,Account account){
this.lock=lock;
this.account=account;
}
@Override
public void run() {
while(true&&account.hasMoney()){
lock.readLock().lock();
try{
System.out.println("~~~~~~~~母亲查看余额 "+account.getAmount());
}finally{
lock.readLock().unlock();
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2 用lock避免顺序死锁,在循环里反复获取两个锁,直到超时
public boolean transferMoney(Account out,Account in,long moneyAmount,long timeout){
while(true){
if(out.lock.tryLock()){
try{
if(in.lock.tryLock()){
try{
out.minus(moneyAmount);
in.add(moneyAmount);
return true;
}finally{
in.lock.unLock();
}
}
}finally{
out.lock.unlock();
}
}
if(System.nanoTime()>timeout){
return false;
}
}
}
如:LinkedBlockingQueue 如果是空,take()方法将会阻塞,一直到有数据为止
如果存在不满足条件时挂起线程,满足条件后立即醒来,将极大简化工作,这就是条件列队AQS AbstractQueuedSynchronizer
简单的条件列队实现
class Buffers{
public sychronized void put(V v)throw InterrputerException {
while(isFull()){ //谓语是isFull,这里不用if,每次唤醒时都必须检查前置条件。notifyAll 唤醒多个谓语时,可能会
改变前置条件,唤醒并不意味着条件满足,除非一对一。
wait();
}
doPut(v);
notifyAll();
}
public sychronized void take()throw InterrputerException {
while(isEmpty()){ //谓语是isEmpty
wait();
}
V v = doGet();
notifyAll();
return v;
}
}
这里要注意丢失的信号
线程必须等待已经为真的条件,但是在开始等待之前没有检查谓语,导致执行过去。
现在线程将等待一个已经发生过的事件。你将不会得到通知
由于多个线程可以基于相同的条件谓语在同一个列队上等待,使用notify而不是notifyall更危险,可能导致丢失信号
这是由于notify唤醒了不是这个谓语的线程,而符合谓语的线程可能永远丢失被唤醒机会(谓语仅仅发生一次)
notifyall 效率低下,引起惊群
只有满足下列两个条件才能使用Notify而不是notifyall
1 谓语动词一样,所有等待线程类型都一样。
2 单进单出。最多只能唤醒一个
用户可以用lock的condition避免这个问题,解决多个谓语动词并发现象。
class Buffers{
private Lock lock = new ReetrantLock();
private Condition notFull =lock.newCondition();
private Condition notEmpty = lock.newCOndition();
public sychronized void put(V v)throw InterrputerException {
lock.lock();
try{
while(isFull()){
notFull.await();
}
doPut(v);
notEmpty.signal();
}finally{
lock.unLock();
}
}
public sychronized void take()throw InterrputerException {
lock.lock();
try{
while(isEmpty()){
notEmpty.await();
}
V v = doGet();
notFull.signal();
return v;
}finally{
lock.unLock();
}
}
}
原子变量和非阻塞同步机制
非阻塞算法,用底层的原子机器指令代替锁来保证并发程序中的一致性,用于操作系统,JVM,垃圾收集器。它使
多个线程竞争相同数据不会发生阻塞,减少上下文开支,没有死锁
当多个线程尝试使用CAS更新一个变量时,只能有一个线程成功,其他的失败。但是失败的线程不会被挂起,这和获取锁不一样,
获取锁线程失败时,它将被挂起。
CAS是构成非阻塞算法重要组成部分。
通过CAS维持包含多个变量的不变性条件
public class NumberRanger{
private static class IntPair {
final int lower;
final int upper; // upper > lower
....
}
private final AtomicReference<Intpair> values = new AtomicReference<IntPair>(new IntPair(0,0));
public int getUpper(){
return values.get().upper;
}
public int getLower(){
return values.get().lower;
}
public void setLower(int i){
while(true){
IntPair oldV = values.get();
if(i>lodV.getUpper()){
throw new IlleglArgumentsException("low is bngger than upper!");
}
IntPair newV = new Intpair(oldV.getUpper,i);
if(values.capareAndSet(oldV,newV)){
reutn;
}
}
}
}