多线程与并发库高级应用



多线程与并发库高级应用

一、传统线程和定时器


传统线程是相对于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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值