Java多线程与并发(三)

  • Condition等待和唤醒

在我们的并行程序中,避免不了某些写成要预先规定好的顺序执行,例如:先新增后修改,先买后卖,先进后出,对于这些场景,使用JUC的Conditon对象再合适不过了。

JUC中提供了Condition对象,用于让指定线程等待与唤醒,按预期顺序执行。它必须和ReentrantLock重入锁配合使用

Condition用于代替wait()/notify()方法
wait和notify是属于Object的,可以对线程唤醒(notify只能随机唤醒等待的线程,而Condition可以唤醒指定线程,这有利于更好的控制并发程序)
Condition核心方法:
await():阻塞当前线程,直到signal唤醒
signal():唤醒被await的线程,从中断处继续执行
signalAll():唤醒所有被await阻塞的线程(不太常用)

通过使用ReentrantLock和Condition的使用让线程有顺序的执行(有规划的、等待、唤醒的过程)

这里写图片描述

代码案例:

public class ConditionSample {
    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock();//Condition必须配合ReentrantLock来使用
        final Condition c1 = lock.newCondition();//创建Condition
        final Condition c2 = lock.newCondition();
        final Condition c3 = lock.newCondition();
        new Thread(new Runnable() {
            public void run() {
                lock.lock();//加锁
                try {
                    c1.await();//阻塞当前线程,只有调用c1.signal的时候,线程继续激活执行
                    Thread.sleep(1000);
                    System.out.println("粒粒皆幸苦");
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }finally{
                    lock.unlock();//释放
                }
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {

                lock.lock();//加锁
                try {
                    c2.await();//阻塞当前线程,只有调用c1.signal的时候,线程继续激活执行
                    Thread.sleep(1000);
                    System.out.println("谁知盘中餐");
                    c1.signal();//线程t1唤醒继续执行
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }finally{
                    lock.unlock();//释放
                }       
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {


                lock.lock();//加锁
                try {
                    c3.await();//阻塞当前线程,只有调用c1.signal的时候,线程继续激活执行
                    Thread.sleep(1000);
                    System.out.println("汗滴禾下土");
                    c2.signal();//线程t2唤醒继续执行
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }finally{
                    lock.unlock();//释放
                }

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                lock.lock();//加锁
                try {
                    Thread.sleep(1000);
                    System.out.println("锄禾日当午");
                    c3.signal();//t3线程继续执行
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }finally{
                    lock.unlock();//释放
                }       
            }
        }).start();
    }
}
  • JUC之Callable和Future
    Callable是一个接口,它和Runnable一样代表着任务,区别在于Callable有返回值并且可以抛出异常
    Future也是一个借口,它用于异步计算的结果,提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果,接受Callable返回的方法
    案例:输出1000以内的质数
public class FutureCallableSample {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        //创建线程池
        for(int i = 2;i<=10000 ;i++){
            Computer c = new Computer();
            c.setNum(i);

            //Future是对用于计算的线程的监听,因为计算是在其它线程中进行的,所以这个返回结果是异步的

            Future<Boolean>  fu = threadPool.submit(c);//将c对象提交给线程池,如果有空闲线程立即执行call方法
            try {
                Boolean result = fu.get();//用于获取返回值,如果线程内部的call没有计算完毕,则进入等待状态,直到计算完成
                if(result){
                    System.out.println(c.getNum());
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        threadPool.shutdown();
    }

}
  • JUC之并发容器

这里写图片描述

如何保证既能线程安全,又可以有一定效率
线程安全—-并发容器
ArrayList —-CopyOnWriteArrayList —写复制列表
HashSet —–CopyOnWriteArraySet —写复制集合
HashMap —-ConcurrentHashMap —分段锁映射

public class CopyOnWriteArrayListSample {
    public static void main(String[] args) {
        List list = new ArrayList();
        for(int i =0; i<1000;i++){
            list.add(i);
        }
        Iterator it = list.iterator();
        while(it.hasNext()){
            Integer i = (Integer)it.next();
            list.remove(i);
        }
        System.out.println(list);
    }
}

代码会报错:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at com.wanghaoxin.threadpool.CopyOnWriteArrayListSample.main(CopyOnWriteArrayListSample.java:15)

对于ArrayList默认采用连续存储,但是会有并发问题,边读取边删除产生异常

CopyOnWriteArrayList采用即可解决这个问题:

这里写图片描述

public class CopyOnWriteArrayListSample {
    public static void main(String[] args) {
/*      List list = new ArrayList();
        for(int i =0; i<1000;i++){
            list.add(i);
        }
        Iterator it = list.iterator();
        while(it.hasNext()){
            Integer i = (Integer)it.next();
            list.remove(i);
        }
        System.out.println(list);*/
        //写复制列表
        List list = new CopyOnWriteArrayList();
        for(int i =0; i<1000;i++){
            list.add(i);
        }
        Iterator it = list.iterator();
        while(it.hasNext()){
            Integer i = (Integer)it.next();
            list.remove(i);
        }
        System.out.println(list);
    }
}

写复制列表:CopyOnWriteArrayList并发原理:
它通过副本解决并发问题
多个线程各执一份副本,采用副本的方式,再复制的过程中,不同线程是同步的
查看源码得知:采用可重入锁

    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

ConcurrentHashMap
Segment——分段锁,提高了批量同步的性能
HashMap线程不安全的,输出值总是小于5000,但是如果代码中修改为HashTable则输出值为5000(HashTable中所有方法都是synchronized 只允许同一时间同一线程修改)
为了解决效率低下的问题,产生了ConcurrentHashMap,输出值始终为5000—表示线程安全

public class ConcurrentHashMapSample {

    private static int user = 100;//同时模拟的并发用户访问数量 --为1的话看不出来效果
    //private static int user = 10;
    private static int dowloadCounts = 5000;//用户的真实下载数
    private static HashMap count= new HashMap();//计数器

    public static void main(String[] args) {
        //调度器,jdk1.5之后引入current对于并发的支持

        ExecutorService executor = Executors.newCachedThreadPool();
        //信号量 ,用于模拟并发用户数
        final Semaphore semaphore = new Semaphore(user);
        for(int i =0;i<dowloadCounts ;i++){
            final int index = i;
            //通过多线程模拟多个用户访问的下载次数
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try{
                        semaphore.acquire();
                        count.put(index, index);
                        semaphore.release();
                    }catch(Exception e){
                        e.printStackTrace();
                    }

                }
            });
        }
        try {
            //延迟主线程结束--让for循环中代码执行完毕
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count.size());
    }
}

ConcurrentHashMap实现原理:
采用分段锁Segment机制
HashTable:所有操作方法都是同步的,其他线程必须等待
这样效率很低

ConcurrentHashMap把区域分为一个个的很小区域,segment,不同线程访问不同的数据,只要不是同一段内都可以操作,但是如果操作的数据在同一个段内,这样需要线程排队

这里写图片描述

这样效率很快,(极限)16倍,把原来的存储空间进行分段加锁处理,段的长度都是2的n次方

  • *JUC之Atomic余CAS算法(乐观锁)
    原子性:是指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行

Atomic包是java.util.concurrent下的另一个专门为线程安全设计的java包,包含多个原子操作类
Atomic包:
这里写图片描述

代码:

public class AtomicIntegerSample {


    private static int user = 10;//同时模拟的并发用户访问数量
    //private static int user = 10;
    private static int dowloadCounts = 5000;//用户的真实下载数
    private static AtomicInteger count= new AtomicInteger(0);

    public static void main(String[] args) {
        //调度器,jdk1.5之后引入current对于并发的支持

        ExecutorService executor = Executors.newCachedThreadPool();
        //信号量 ,用于模拟并发用户数
        final Semaphore semaphore = new Semaphore(user);
        for(int i =0;i<dowloadCounts ;i++){
            //通过多线程模拟多个用户访问的下载次数
            executor.execute(new Runnable() {

                @Override
                public void run() {
                    try{
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    }catch(Exception e){
                        e.printStackTrace();
                    }

                }
            });
        }
        try {
            //延迟主线程结束--让for循环中代码执行完毕
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }
/*  private synchronized static void add(){
        count++;
    }*/
    private  static void add(){
        count.getAndIncrement();//count++
    }
    //此处并没有用synchronized和锁机制


}

CAS算法:
锁是用来做并发的最简单的机制,当然其代价也是很高的,独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致所有需要锁的线程刮起,等待持有锁的线程释放锁

所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
其中CAS(比较与交换 Compare And Swap ),是一种有名的无锁算法

这里写图片描述

比较的是期望值与实际操作的结果

Atomic的应用场景:
虽然基于CAS的线程安全机制很好很搞笑,但是要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合于一些锁粒度比较小型:例如计数器这样的需求用起来才更有效果,否则也不会有锁的存在了

对于大量数据操作反而会损耗性能,因为做了很多次

  • 总结

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_19704045/article/details/81711581