多线程与并发库高级应用

来源:互联网 发布: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间隔时间);

TimerTaskRunnable类似,有一个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实现:

ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetCopyOnWriteArrayList CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap通常优于同步的 HashMapConcurrentSkipListMap通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的 ArrayList

ConcurrentSkipListMap<K,V>映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的 Comparator进行排序,具体取决于使用的构造方法。

ConcurrentSkipListSet<E>一个基于ConcurrentSkipListMap的可缩放并发 NavigableSet实现。set的元素可以根据它们的自然顺序进行排序,也可以根据创建 set时所提供的 Comparator进行排序,具体取决于使用的构造方法

CopyOnWriteArrayList<E>ArrayList的一个线程安全的变体,其中所有可变操作(addset等等)都是通过对底层数组进行一次新的复制来实现的。





0 0
原创粉丝点击