多线程之三:JDK并发包

1. 各种同步控制工具的使用

    1.1.ReentrantLock

        1.1.1. 可重入 单线程可以重复进入,但要重复退出

        1.1.2. 可中断 lockInterruptibly()

        1.1.3. 可限时 超时不能获得锁,就返回false,不会永久等待构成死锁

        1.1.4. 公平锁 先来先得 (相对不公平锁,线程调度的效率会差点)

        

public ReentrantLock(boolean fair)

public static ReentrantLock fairLock = new ReentrantLock(true);

        公平是有代价的。如果您需要公平,就必须付出代价,但是请不要把它作为您的默认选择。

        1.1.5. 代码示例

        

Public class Test{
			Lock lock = new ReentrantLock();  
			Public void Method1(){
				try {   
					lock.lockInterruptibly (); 
					// update object state  
				}
				finally {
					lock.unlock();   
				}
			}
	}

        可以看到 Lock synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。

    1.2.Condition

        1.2.1. 概述

        类似于 Object.wait()Object.notify()

        与ReentrantLock结合使用

             Lock 对象则充当绑定到这个锁的条件变量的工厂对象,与标准的 wait  notify 方法不同,对于指定的 Lock ,可以有不止一个条件变量与它关联。这样就简化了许多并发算法的开发。例如, 条件(Condition Javadoc 显示了一个有界缓冲区实现的示例,该示例使用了两个条件变量,“not full”和“not empty”,它比每个 lock 只用一个 wait 设置的实现方式可读性要好一些(而且更有效)。

        1.2.2. 主要接口

        

void await() throws InterruptedException;

void awaitUninterruptibly();

long awaitNanos(long nanosTimeout) throws InterruptedException;

boolean await(long time, TimeUnit unit) throws InterruptedException;

 boolean awaitUntil(Date deadline) throws InterruptedException;

void signal();

void signalAll();

        1.2.3. API详解

            await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线 程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。

            awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。

            singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obej ct.notify()方法很类似

        1.2.4.代码示例

        

public class BlockQueue<T> {
	private ReentrantLock lock = new ReentrantLock();
	private Condition FullCondition = lock.newCondition();
	private Condition EmptyCondition = lock.newCondition();
	private List<T> list = new ArrayList<T>() ;
	private int Max;
	
	public BlockQueue(int size){
		Max = size;
	}
	
	public boolean push(T t){
		try{
			lock.lock();
			while(list.size()>=Max){
				FullCondition.await();
			}
			list.add(t);
			EmptyCondition.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
//		synchronized(list){
//			while(true){
//				if(list.size()>=Max){
//					try{
//						list.wait();
//					}catch(Exception e){
//						e.printStackTrace();
//					}
//				}else{
//					break;
//				}
//			}
//			list.add(t);
//			
//			list.notify();
//		}
		return true;
	}
	

	
	public T poll(){
		T t = null;
		try{
			lock.lock();
			while(list.isEmpty()){
				EmptyCondition.await();
			}
			t = list.get(0);
			list.remove(0);
			FullCondition.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
//		synchronized(list){
//			while(true){
//				if(list.isEmpty()){
//					try{
//						list.wait();
//					}catch(Exception e){
//						e.printStackTrace();
//					}
//				}else{
//					break;
//				}
//			}
//			t = list.get(0);
//			list.remove(0);
//			list.notify();
//		}
		
		return t;
	}
}

        该示例是自定义阻塞队列的实现,从代码可以看出,Object. notify ()会对该Object所有wait队列中随机唤醒一个,可能唤醒的不是我们想要的,例如读操作唤醒了读操作,所以可能会造成死锁。而Condition可以有多个,所以可以有目的的唤醒操作。例如只唤醒读操作或写操作。

    1.3. Semaphore(信号) 

        1.3.1. 概述 共享锁 运行多个线程同时临界区

        1.3.2. 主要接口

            

public void acquire()

public void acquireUninterruptibly()

public boolean tryAcquire()

public boolean tryAcquire(long timeout, TimeUnit unit)

public void release()

        1.3.3.代码示例

            

public class TestSemaphore {
                public static void main(String[] args) {
                // 线程池
                ExecutorService exec = Executors.newCachedThreadPool();
                // 只能5个线程同时访问
                final Semaphore semp = new Semaphore(5);
                 // 模拟20个客户端访问
                 for (int index = 0; index < 20; index++) {
                              final int NO = index;
                              Runnable run = new Runnable() {
                                                 public void run() {
                                                            try {
                                                                    // 获取许可
                                                                    semp.acquire();
                                                                    System.out.println("Accessing: " + NO);
                                                                    Thread.sleep((long) (Math.random() * 10000));
                                                                    // 访问完后,释放
                                                                    semp.release();
                                                                    System.out.println("-----------------"+semp.availablePermits());
                                                            } catch (InterruptedException e) {
                                                                    e.printStackTrace();
                                                            }
                                                  }
                                      };
                      exec.execute(run);
             }
             // 退出线程池
             exec.shutdown();
       }
}

        Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数

    1.4.ReadWriteLock

        1.4.1. 概述

            ReadWriteLockJDK5中提供的读写分离锁

        读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁

        1.4.2. 访问情况

        读-读不互斥:读读之间不阻塞。

        读-写互斥:读阻塞写,写也会阻塞读。

        写-写互斥:写写阻塞。

             

        1.4.3. 主要接口

private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();

private static Lock readLock = readWriteLock.readLock();

private static Lock writeLock = readWriteLock.writeLock();

        1.4.4. 代码示例 

class Queue3{
    private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    public void get(){
        rwl.readLock().lock();//上读锁,其他线程只能读不能写
        System.out.println(Thread.currentThread().getName() + " be ready to read data!");
        try {
            Thread.sleep((long)(Math.random()*1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
			rwl.readLock().unlock(); //释放读锁,最好放在finally里面
		}
        System.out.println(Thread.currentThread().getName() + "have read data :" + data);       
        
    }
    public void put(Object data){
        rwl.writeLock().lock();//上写锁,不允许其他线程读也不允许写
        System.out.println(Thread.currentThread().getName() + " be ready to write data!");                   
        try {
            Thread.sleep((long)(Math.random()*1000));
			this.data = data;  
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
			rwl.writeLock().unlock();//释放写锁 
		}     
        System.out.println(Thread.currentThread().getName() + " have write data: " + data);                   
    }
}

        代码中get方法可以多个线程同时访问,当put方法被调用时会等正在进行的get方法全部释放了读锁,然后争取到写锁。但是写锁只能1个线程同时拥有,读锁可以多个线程同时拥有。

 

    1.5.CountDownLatch  

        1.5.1. 概述 

        倒数计时器 

        一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程 ,等待所有检查线程全部完工后,再执行主线程 

        1.5.2. 主要接口

static final CountDownLatch end = new CountDownLatch(10);

end.countDown();

end.await();

        1.5.3. 示意

         

        1.5.4. 代码示例

public class Test {

     public static void main(String[] args) {  

         final CountDownLatch latch = new CountDownLatch(2);

 

         new Thread(){

             public void run() {

                 try {

                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");

                    Thread.sleep(3000);

                    System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");

                    latch.countDown();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

             };

         }.start();

 

         new Thread(){

             public void run() {

                 try {

                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");

                     Thread.sleep(3000);

                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");

                     latch.countDown();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

             };

         }.start();

 

         try {

             System.out.println("等待2个子线程执行完毕...");

            latch.await();

            System.out.println("2个子线程已经执行完毕");

            System.out.println("继续执行主线程");

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

     }

}

 

    1.6.CyclicBarrier

        1.6.1. 概述 

        Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程

        1.6.2. 主要接口

            public CyclicBarrier(int parties, Runnable barrierAction)

                barrierAction就是当计数器一次计数完成后,系统会执行的动作 await()

        1.6.3. 示意

         

        1.6.4. 代码示例

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程"+Thread.currentThread().getName());   
            }
        });
 
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务...");
        }
    }
}


        输出结果:

        线程Thread-0正在写入数据...

        线程Thread-3正在写入数据...

        线程Thread-2正在写入数据...

        线程Thread-1正在写入数据...

        线程Thread-2写入数据完毕,等待其他线程写入完

        线程Thread-0写入数据完毕,等待其他线程写入完

        线程Thread-3写入数据完毕,等待其他线程写入完

        线程Thread-1写入数据完毕,等待其他线程写入完

     当前线程Thread-3

        所有线程写入完毕,继续处理其他任务...

        所有线程写入完毕,继续处理其他任务...

        所有线程写入完毕,继续处理其他任务...

        所有线程写入完毕,继续处理其他任务...

 

        从上面输出结果可以看出,每个写入线程执行完写数据操作之后,就在等待其他线程写入操作完毕。

当所有线程线程写入操作完毕之后,所有线程就继续进行后续的操作了。

 

    1.7. LockSupport

        1.7.1. 概述 

        提供线程阻塞原语

        1.7.2. 主要接口 

                LockSupport.park(); 

                LockSupport.unpark(t1);

        1.7.3. suspend()比较 

        不容易引起线程冻结

        1.7.4. 中断响应

        能够响应中断,但不抛出异常。 中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志

    1.8.ReentrantLock 的实现

        1.8.1. CAS状态

        1.8.2. 等待队列  

        1.8.3. park()

2. 并发容器

    2.1.集合包装

        为集合类包了一层衣服,为每个方法添加了同步的实现。

    2.1.1. HashMap

        Collections.synchronizedMap

        public static Map m=Collections.synchronizedMap(new HashMap());

    2.1.2. List

        synchronizedList

    2.1.3. Set

        synchronizedSet

    2.2.ConcurrentHashMap

     高性能HashMap

     看起来是一个Map数据结构,内部实现将其划分为几个小的Map,而且每个Map都有独立的锁,形成分区锁。每个分区的读写操作不影响另一个分区。

    2.3.BlockingQueue

     阻塞队

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值