Java高新技术7_多线程2(原子性操作,线程池,读写锁,Condition,Callable&Future)
1.原子性操作:
package com.itheima.thread.current; import java.util.concurrent.atomic.AtomicInteger; /* Atomic(原子性):原子性:它表示一个操作序列就像一个操作一样不被打断 当有多个线程共享数据data,在不使用同步的情况下 data=1; data=data+1(data++);//假设有两个线程,当其中一个线程执行完data+1,CPU切换到另一个线程执行data=data+1->data=2 //原本data最终值应该为3,但由于缺少一次赋值动作导致data=2原子性操作保证data=data+1的操作不被打断 */ public class AutomicDemo1 { /** * @param args */ private static AtomicInteger data=new AtomicInteger(1); // private static int data=1; public static void main(String[] args) { // TODO 自动生成的方法存根 for(int i=0;i<10;++i) new Thread(new Runnable(){ @Override public void run(){ data.addAndGet(2); //data=data+2; System.out.println(Thread.currentThread().getName()+"..."+data); } }).start(); try{Thread.sleep(10);}catch(Exception e){} //System.out.println(data.get()); System.out.println(data); } } /* 如果该共享数据为某个类中的成员变量该如何操作? AtomicIntegerFieldUpdater<T> T - 保持可更新字段的对象类型。 public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) 将指定类的指定字段封装成一个更新器对象 int addAndGet(T obj, int delta) 对该类的那个obj操作 以原子方式将给定值add到此更新器管理的给定对象的字段当前值。 */采用AtomicInteger操作:最终data的值恒为21
出现多个9的原因是因为:当某一个线程加到9之后,cpu切换到其他线程执行输出语句.
采用注释的运行结果可能出现:
2.线程池:
如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能。
如果真的来一名学员,我们都安排一名新工作人员为之服务,也是不可能的,那公司岂不是要招聘很多工作人员(线程)?
而是应该一名工作人员服务完一名学员,空闲下来后,一旦有新的学员要服务,我又立即安排该工作人员为新学员服务。线程池的概念与此类似,首先创建一些线程,它们的集合称为线程池,
当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,
而是将该线程还回到线程池中。在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,
线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,这就是封装。
当任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务package com.itheima.thread.current; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool2 { /** * @param args */ public static void main(String[] args) { // TODO 自动生成的方法存根 //ExecutorService threadPool=Executors.newFixedThreadPool(3);//线程数目固定的线程池 // ExecutorService threadPool=Executors.newCachedThreadPool();//创建一个可根据需要创建新线程的线程池 //如果现有线程没有可用的,则创建一个新线程并添加到池中。 //终止并从缓存中移除那些已有 60 秒钟未被使用的线程 ExecutorService threadPool=Executors.newSingleThreadExecutor();//创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 //其实现了当线程死掉后,重新启动(指的是启动它的一个替补) //(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程, //那么如果需要,一个新线程将代替它执行后续的任务 int j; for(j=0;j<5;++j){ final int task=j; threadPool.execute(new Runnable(){ @Override public void run() { for(int i=0;i<4;++i) System.out.println(Thread.currentThread().getName() +"...loop..."+i+"...for the task..."+task); } }); } System.out.println("tasks have been submmitted..."+j); //threadPool.shutdown();//启动一次顺序关闭,执行完以前提交的任务(已提交的五个任务),但不接受新任务。 //threadPool.shutdownNow();//试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。 //线程池中只有3个线程,每个线程处理一个任务,当处理完这三个任务,直接停止. } }FixedThreadPool: (一直在使用三个线程执行任务,没有线程执行该任务时,该任务等待)
CachedThreadPool: (根据需要创建线程)
SingleExecutor:可保证顺序地执行各个任务,在执行完当前任务在执行下一个任务.
线程池与计时器:
package com.itheima.thread.current; import java.util.concurrent.*; public class TimerThreadPool3 { /** * @param args */ public static void main(String[] args) { // TODO 自动生成的方法存根 //计时器与线程池 Executors.newScheduledThreadPool(3).schedule(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 System.out.println("BOMB!!!!"); } }, 3,TimeUnit.SECONDS);//3秒后爆炸 Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 System.out.println("BOMB!!!!"); } }, 3,4,TimeUnit.SECONDS); } } /* 要安排在某个以后的 Date 运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。 */Callable&Future:
Callable
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
Future
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。package com.itheima.thread.current; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableAndFuture4 { /** * @param args */ /* Callable 返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。 Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。 但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。 Future Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。 计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。 */ public static void main(String[] args) { // TODO 自动生成的方法存根 ExecutorService threadPool=Executors.newSingleThreadExecutor(); Future<String> future=threadPool.submit(new Callable<String>(){ @Override public String call() throws Exception { // TODO 自动生成的方法存根 Thread.sleep(3000); return "success"; } }); try { System.out.println(future.get());//该方法阻塞,等待结果 } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } catch (ExecutionException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } }CompletionService:
/*CompletionService用于提交一组Callable任务, 其take方法返回已完成的一个Callable任务对应的Future对象。*/ class CompletionServiceDemo{ public static void main(String[] args){ ExecutorService threadPool=Executors.newFixedThreadPool(5); CompletionService<Integer> completionService=new ExecutorCompletionService<Integer> (threadPool);//返回结果Integer //提交5个任务 for(int i=0;i<5;++i){ final int task=i; completionService.submit(new Callable<Integer>(){ @Override public Integer call() throws Exception { // TODO 自动生成的方法存根 Thread.sleep(new Random().nextInt(5));//随机0~4秒 return task; } }); } //获取下一个已完成的任务结果 while(true) try { System.out.println(completionService.take().get());//获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。 } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } catch (ExecutionException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } }
3.读写锁:
分为读锁和写锁,
多个读锁不互斥(多个线程可以并发读),读锁与写锁互斥(当读数据时,不能写数据,当写数据时,也不能读数据),
写锁与写锁互斥(每次只允许一个线程写数据),这是由jvm自己控制的,如果单纯使用同步很难做到.
我们只要只要上好相应的锁即可.
如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,
只能有一个人在写,且不能同时读取,那就上写锁。
总之,读的时候上读锁,写的时候上写锁!class ReadWrite{ private int data; private ReadWriteLock rwl=new ReentrantReadWriteLock(); public void read(){ try{ Thread.sleep(1); } catch(Exception e){ } rwl.readLock().lock(); try{ System.out.println(Thread.currentThread().getName()+ "...read is coming..."); System.out.println(Thread.currentThread().getName()+ "...read the data..."+data); }finally{ rwl.readLock().unlock(); } } public void write(int data){ rwl.writeLock().lock(); try{ System.out.println(Thread.currentThread().getName()+"..." +data+"...will be written..."); this.data=data; System.out.println(Thread.currentThread().getName()+"..." +data+"...has been written..."); }finally{ rwl.writeLock().unlock(); } } } public class ReadWriteLockDemo5 { /** * @param args */ private static ReadWrite rw=new ReadWrite(); public static void main(String[] args) { // TODO 自动生成的方法存根 //十个个线程读操作 for(int i=0;i<10;++i) new Thread(){ @Override public void run(){ rw.read(); } }.start(); for(int i=0;i<3;++i) new Thread(){ @Override public void run(){ rw.write(new Random().nextInt(100)); } }.start(); //在同一个ReentrantReadWriteLock实例上readLock返回的同一个读锁对象,writeLock //返回的是同一个写锁对象 /*ReadWriteLock rwl=new ReentrantReadWriteLock(); System.out.println((rwl.readLock()==rwl.readLock()) +"\n"+(rwl.writeLock()==rwl.writeLock()));//true,true*/ } }简单的模拟缓存: (思想):
//模拟缓存(仿照API中的ReentrantReadWriteLock示例) class CustomCache{ private Object data=null; private Map hashMap=new HashMap(); private ReadWriteLock lock=new ReentrantReadWriteLock(); public Object getData(Object key){ lock.readLock().lock(); try{ if(data==null){ lock.readLock().unlock();//当缓存中没有对应data,释放掉读锁,使用写锁//① lock.writeLock().lock();//② try{//获取data的过程中可能发生异常 if(data==null)//该判断目的,假设二个线程执行到①,其中一个进来为data赋值,赋值后 //writeLock释放,第二个线程进来,会再次赋值 data=hashMap.get(key); lock.readLock().lock(); } finally{ lock.writeLock().unlock();//也可以先释放写入锁,在获取读取锁,完成锁降级 } } }finally{ lock.readLock().unlock(); } return data; } } /* API中一句话: writer 可以获取读取锁,但反过来则不成立。 在其他应用程序中,当在调用或回调那些在读取锁状态下执行读取操作的方法期间保持写入锁时,重入很有用。 如果 reader 试图获取写入锁,那么将永远不会获得成功。 锁降级 重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。 但是,从读取锁升级到写入锁是不可能的,除非释放掉读取锁,再去获取写入锁. */
4.多线程通信(Lock&Condition)
/* 举例对比多线程通信新老特性: 一.所有生产者执行相同代码,所有消费者执行相同代码,使用同一个标记 1.一个生产者,一个消费者->使用同步避免了 在生产时不能消费,在消费时不能生产,但是可能出现不停的生产或不停消费 ->要想达到生产一个消费一个,使用线程间的通信,生产一个后,生产者等待wait,唤醒消费者notify. 2.多个生产者,多个消费者->依然想生产一个消费一个,生产以后,生产者等待,唤醒消费者notifyAll(唤醒所有,防止唤醒另一个生产者,全部等待),消费者同理. ->另一种解决方式:在Lock绑定两个condition实例,生产以后,生产者在condition1.await()等待, condition2.singal();(唤醒消费者),消费者同理. 二.A,B,C分别代表不同代码,要求当一个线程执行完A唤醒另一个线程执行B, B执行完唤醒线程执行C,C执行完唤醒线程执行A 方式一: int flag=1; Object obj=new Object(); A: B: while(flag!=1) while(flag!=2) obj.wait(); obj.wait(); ... ... flag=2; flag=3; obj.notifyAll(); obj.notifyAll();//notifyAll可以避免全部等待 C: while(flag!=3) obj.wait(); ... flag=1; obj.notifyAll(); 方式二 : int flag=1; Lock lock=new ReentrantLock(); Condition con1=lock.newCondition(); Condition con2=lock.newCondition(); Condition con3=lock.newCondition(); A: lock.lock(); while(flag!=1) con1.await(); ... falg=2; con2.signal(); lock.unlock(); B: lock.lock(); while(flag!=2) con2.await(); ... falg=3; con3.signal(); lock.unlock(); C: lock.lock(); while(flag!=3) con3.await(); ... falg=1; con1.signal(); lock.unlock(); 方式二对比方式一:方式一会唤醒所有线程,会多一些无用的判断,但是只用了一个锁实例, 而方式二用了三个condition实例绑定到同一个lock实例上 */