多线程与并发库高级应用
来源:互联网 发布:javascript 表格并排放 编辑:程序博客网 时间:2024/05/23 16:44
多线程与并发库高级应用
一、传统线程和定时器
传统线程是相对于JDK1.5而言的。
1.传统线程创建的两种方式为:
1)创建Thread的子类,覆盖其中的run方法,运行这个子类的start方法即可开启线程
2)创建Thread时传递一个实现Runnable接口是对象实例
这些知识点的详细信息都在我的另一篇博客——多线程中,如想了解可以查看那片博客。
传统定时器的创建:直接使用定时器类Timer
a、过多长时间后炸
newTimer().schedule(TimerTask定时任务, Date time定的时间);
b、过多长时间后炸,以后每隔多少时间再炸
newTimer().schedule(TimerTask定时任务, Long延迟(第一次执行)时间, Long间隔时间);
TimerTask与Runnable类似,有一个run方法
Timer是定时器对象,到时间后会触发炸弹(TimerTask)对象
如:
new Timer().schedule(new TimerTask()//定时执行的任务{public void run(){SOP(“bombing”);}//显示计时信息while (true){SOP(new Date().getSeconds());Thread.sleep(1000);}},10//定好的延迟时间,10秒以后执行任务);
3.传统线程互斥方法:
a、同步代码块
synchronized(lock){}
b、同步方法
方法返回值前加synchronized
同步方法上边用的锁就是this对象。
静态同步方法使用的锁是该方法所在的class文件对象。
使用synchronized关键字实现互斥,要保证同步的地方使用的是同一个锁对象。
4.传统线程通信
传统的线程间通信都会使用snychronized与方法wait()等待notify() 唤醒最先等待的那个线程 notifyAll()唤醒所有 ,这三个方法都在Object类中,而Object是所有类的超类。
在JDK1.5之后使用Lock和Condition,Lock,体系中,把wait() notify() notifyAll()封装为一个Condition对象,(一个Lock可以对应多个Condition对象,)那么同时等待与唤醒就有了对应关系,就是说我的等待只能我唤醒,其就具有了从属关系,每当唤醒与我同一个对象里的等待,与线程等待先后没有关系,那么便可以通过等待与唤醒放置位置的不同,灵活的控制不同线程的等待与唤醒。
二、线程并发库
线程并发库是JDK1.5后出现的特性。
1.线程池
线程池:先创建多个线程放在线程池中,当有任务需要执行时,从线程池中找一个空闲线程执行任务,任务完成后,并不销毁线程,而是返回线程池,等待新的任务安排。
线程池编程中,任务是提交给整个线程池的,并不是提交给某个具体的线程,而是由线程池从中挑选一个空闲线程来运行任务。一个线程同时只能执行一个任务,可以同时向一个线程池提交多个任务。
线程池创建方法:
a、创建一个拥有固定线程数的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
b、创建一个缓存线程池,线程池中的线程数根据任务多少自动增删动态变化
ExecutorService threadPool = Executors.newCacheThreadPool();
c、创建一个只有一个线程的线程池 与单线程一样 但好处是保证池子里有一个线程,
当线程意外死亡,会自动产生一个替补线程,始终有一个线程存活
ExecutorService threadPool = Executors.newSingleThreadExector();
还可以用线程池来启动定时器
用线程池启动定时器:
a、创建调度线程池,提交任务,延迟指定时间后执行任务
Executors.newScheduledThreadPool(线程数).schedule(Runnable, 延迟时间,时间单位);b、创建调度线程池,提交任务, 延迟指定时间执行任务后,间隔指定时间循环执行
Executors.newScheduledThreadPool(线程数).schedule(Runnable, 延迟时间,间隔时间,时间单位);所有的schedule方法都接受相对 延迟和周期作为参数,而不是绝对的时间或日期。
2.Callable与Future的应用
public interface Callable<V>
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call的方法。 Callable接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable不会返回结果,并且无法抛出经过检查的异常。
只有一个方法V call()计算结果,如果无法计算结果,则抛出一个Exception异常。
使用方法:
ExecutorService threadPool = Executors.newSingleThreadExccutor();//如果不需要返回结果,就用executor方法 ,调用submit方法返回一个Future对象Future<T> future = threadPool.submit(new Callable<T>(){//接收一个Callable接口的实例对象//覆盖Callable接口中的call方法,抛出异常public T call() throws Exception{ruturn T}});
Future表示异步计算的结果。
public interface CompletionService<V>
CompletionService用于提交一组Callable任务,其take方法返回一个已完成的Callable任务对应的Future对象。好比同时种几块麦子等待收割,收割时哪块先熟先收哪块。如:
ExecutorService threadPool = Executors.newFixedThreadPool(10);//创建线程池,传递给coms//用threadPool执行任务,执行的任务返回结果都是整数CompletionService<Integer> coms = new ExecutorCompletionService<Integer>(threadPool);//提交10个任务 种麦子 for (int i=0; i<10; i++) {final int num = i+1; coms.submit(new Callable<Integer>(){ public Integer call()//覆盖call方法 {//匿名内部类使用外部变量要用final修饰SOP(任务+num);Thread.sleep(new Random().nextInt(6)*1000);return num; } });}//等待收获割麦子for (int i=0; i<10; i++){//take获取第一个Future对象,用get获取结果SOP(coms.take().get());}
3.线程锁技术
Lock比传统线程模型中的synchronized更加面向对象,锁本身也是一个对象,两个线程执行的代码要实现同步互斥效果,就要使用同一个锁对象。锁要上在要操作的资源类的内部方法中,而不是线程代码中。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用synchronized方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
ReentrantReadWriteLock:读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,由
JVM控制。
下面看一个读写锁的示例:
public class ReadWriteLockDemo{/**读写所使用 * 三个线程读,三个线程写 */public static void main(String[] args){//共享对象final Source source = new Source();//创建线程for (int i=0; i<3; i++){//读new Thread(new Runnable(){public void run(){while (true)source.get();}}).start();//写new Thread(new Runnable(){public void run(){while (true)source.put(new Random().nextInt(999));}}).start();}}static class Source{//共享数据private int data = 0;//要操作同一把锁上的读或写锁ReadWriteLock rwl = new ReentrantReadWriteLock();//读方法public void get(){//上读锁rwl.readLock().lock();try{//获取数据并输出System.out.println("读——"+Thread.currentThread().getName()+"正在获取数据。。。");try{Thread.sleep(new Random().nextInt(6)*1000);} catch (InterruptedException e){e.printStackTrace();}System.out.println("读——"+Thread.currentThread().getName()+"获取到的数据:"+data);}finally{//解锁rwl.readLock().unlock();}}//写方法public void put(int data){//上写锁rwl.writeLock().lock();try{//提示信息System.out.println("写——"+Thread.currentThread().getName()+"正在改写数据。。。");try{Thread.sleep(new Random().nextInt(6)*1000);} catch (InterruptedException e){e.printStackTrace();}this.data = data;System.out.println("写——"+Thread.currentThread().getName()+"已将数据改写为:"+data);}finally{//解锁rwl.writeLock().unlock();}}}}
4.条件阻塞Condition
Condition的功能类似在传统线程技术中的Object.wait()和Object.natify()的功能,传统线程技术实现的互斥只能一个线程单独干,不能说这个线程干完了通知另一个线程来干,Condition就是解决这个问题的,实现线程间的通信。
使用方法:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
this.wait()condition.await()
this.notify()condition.signal()
注意:判断条件时用while防止虚假唤醒,等待在那里,唤醒后再进行判断,确认符合要求后再执行任务。
5.Semaphonre同步工具
Semaphore可以维护当前访问自身的线程个数,并且提供了同步机制。
Semaphore实现的功能类似于厕所里有5个坑,有10个人要上厕所,同时就只能有5个人占用,当5个人中 的任何一个让开后,其中在等待的另外5个人中又有一个可以占用了。
例如,下面的类使用信号量控制对内容池的访问:
class Pool { private static final int MAX_AVAILABLE = 100;//最大允许的量 private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); public Object getItem() throws InterruptedException { available.acquire(); return getNextAvailableItem(); } public void putItem(Object x) { if (markAsUnused(x)) available.release(); } // 不是一个典型有效的数据结构,just for demo protected Object[] items = ... whatever kinds of items being managed protected boolean[] used = new boolean[MAX_AVAILABLE]; protected synchronized Object getNextAvailableItem() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } } return null; // not reached } protected synchronized boolean markAsUnused(Object item) { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (item == items[i]) { if (used[i]) { used[i] = false; return true; } else return false; } } return false; } }
6.CountDownLatch同步工具
好像倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当到达0时,所有等待者就开始执行。
举例:多个运动员等待裁判命令:裁判等所有运动员到齐后发布结果。代码示例:
ExecutorService service = Executors.newCachedThreadPool();//裁判发布命令的计数器,计数器为0,运动员就跑final CountDownLatch cdOrder = new CountDownLatch(1);//运动员跑到终点的计数器,为0裁判宣布结果final CountDownLatch cdAnswer = new CountDownLatch(3);//产生3个运动员for (int i=0; i<3; i++){//运动员的任务Runnable runnable = new Runnable(){public void run(){SOP(ThreadName准备接受命令)//等待发布命令cdOrder.await();//计数器为0继续向下执行SOP(ThreadName已接受命令)//order计数器为0了Thread.sleep(Random);//开始跑步cdAnswer.countDown();//跑到终点了,计数器减1}};service.execute(runnable);运动员开始任务}Thread.sleep(1000);//裁判休息一会 再发布命令SOP(即将发布命令);cdOrder.countDown();//命令计数器置为0,发布命令SOP(命令已经发布,等待结果);cdAnswer.await();// 等待所有运动员,计数器为0 所有运动员到位SOP(宣布结果);
7.Exchanger同步工具
用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人会一直等待第二个人,直到第二个人拿着数据到来时,才能彼此交换数据。
用法示例:以下是重点介绍的一个类,该类使用Exchanger在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获取一个新腾空的缓冲区,并将填满的缓冲区传递给腾空缓冲区的线程。
class FillAndEmpty { Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();//得到一个Exchanger对象 DataBuffer initialEmptyBuffer = ... a made-up type DataBuffer initialFullBuffer = ... class FillingLoop implements Runnable { public void run() { //重写run方法 DataBuffer currentBuffer = initialEmptyBuffer; try { while (currentBuffer != null) { addToBuffer(currentBuffer); if (currentBuffer.isFull()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ... } } } class EmptyingLoop implements Runnable { public void run() { //重写run方法 DataBuffer currentBuffer = initialFullBuffer; try { while (currentBuffer != null) { takeFromBuffer(currentBuffer); if (currentBuffer.isEmpty()) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { ... handle ...} } } void start() { new Thread(new FillingLoop()).start();//开启两个线程 new Thread(new EmptyingLoop()).start(); } }
8.阻塞队列的应用
队列包含固定长度的队列和不固定长度的队列,先进先出。
固定长度的队列往里放数据,如果放满了还要放,阻塞式队列就会等待,直到有数据取出,空出位置后才继续放;非阻塞式队列不能等待就只能报错了
讲Condition时提到了阻塞队列的原理,Java中已经实现了阻塞队列ArrayBlockingQueue
BlockingQueue<E> public interfaceBlockingQueue<E>extendsQueue<E>支持两个附加操作的Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。
BlockingQueue方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。
BlockingQueue实现主要用于生产者-使用者队列,但它另外还支持Collection接口。因此,举例来说,使用remove(x)从队列中移除任意一个元素是有可能的。然而,这种操作通常不会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
9.同步集合类
传统集合实现同步的问题:
使用同步的Map集合 使用集合工具类中的方法将不同步的集合转为同步的Collections.synchronizedMap(newMap())这个方法返回一个同步的集合。
public static <K, V> Map<K, V> synchronizedMap(Map<K, V> m){return new SynchronizedMap<K, V>(m);}
JDK1.5中提供了并发 Collection:提供了设计用于多线程上下文中的 Collection实现:
ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和 CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap通常优于同步的 HashMap,ConcurrentSkipListMap通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的 ArrayList。
ConcurrentSkipListMap<K,V>映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的 Comparator进行排序,具体取决于使用的构造方法。
ConcurrentSkipListSet<E>一个基于ConcurrentSkipListMap的可缩放并发 NavigableSet实现。set的元素可以根据它们的自然顺序进行排序,也可以根据创建 set时所提供的 Comparator进行排序,具体取决于使用的构造方法
CopyOnWriteArrayList<E>ArrayList的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。
- Java多线程与并发库高级应用
- 多线程与并发库高级应用
- Java多线程与并发库高级应用
- Java多线程与并发库高级应用
- 多线程与并发库高级应用
- Java多线程与并发库高级应用
- 视频专辑:张孝祥 Java多线程与并发库高级应用
- Java多线程与并发库高级应用之线程池
- Java多线程与并发库高级应用之信号量Semaphore
- 黑马程序员_java多线程与并发库高级应用
- Java多线程与线程并发库高级应用笔记
- Java多线程与并发库高级应用之线程池
- 张孝祥_Java多线程与并发库高级应用01
- 张孝祥_Java多线程与并发库高级应用02
- 张孝祥_Java多线程与并发库高级应用03
- 张孝祥_Java多线程与并发库高级应用04
- 张孝祥_Java多线程与并发库高级应用05
- Java多线程与并发库高级应用05
- iOS剪切板、长按复制
- bootstrap 学习笔记 - 5 (Glyphicons字体 + 下拉菜单 + 按钮组+ 按钮下拉菜单)
- Java语言描述:回溯法之批处理作业调度
- 推荐学习网站
- 黑马程序员_java语言_IO其他流
- 多线程与并发库高级应用
- 一步一步 实现智能指针(三)
- LightOJ1007---Mathematically Hard (欧拉函数)
- OpenCV学习笔记09--通过cvPtr2D或指针算法绘制图形
- 青春
- 泰国清迈8天自由行小结
- DLL的进入退出——由DLL_PROCESS_ATTACH而联系搜索起来。
- [C#] 汉字转拼音,支持多音字
- 排列组合算法之三: 递归法