快速了解JUC(二)

一、集合不安全问题

在并发的情况下,集合也是不安全的。

1.1 ArrayList

public class Test01 {
    public static void main(String[] args) {
          //并发下的ArrayList不安全
        //解决方案:
//        List list = new Vector<>();
//        List list = Collections.synchronizedList(new ArrayList<>());
//        List list = new CopyOnWriteArrayList<>();
        //CopyOnWrite写入时复制 cow 计算机程序设计领域的一种优化策略;
        //多个线程调用的时候, list ,读取的时候 ,固定的,写入(覆盖)
        //在写入的时候避免覆盖,造成数据问题
        //读写分离
        //CopyOnWriteArrayList 比 Vector 厉害在哪里?
        //因为Vector的还是通过synchronized来实现的,而CopyOnWriteArrayList是通过lock来实现的,只要
        //是通过synchronized来实现的效率就是低。为什么synchronized这个效率低呢?
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i <10 ; i++) {
            new Thread( () ->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list + "\t");
            },String.valueOf(i)).start();
        }
    }
}

1.2 Set集合

public class Test02 {
    public static void main(String[] args) {
        /**
        * Set集合在并发下也是不安全的
         *会出现 ConcurrentModificationException 这个异常错误
         * 解决方案:
         * 1.Set set = new HashSet<>();
         * 2.Set set = Collections.synchronizedSet(new HashSet<>());
         * 3.Set set = new CopyOnWriteArraySet<>();
        * */
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

3. Map

public class Test03 {
    public static void main(String[] args) {
        /**
         * Set集合在并发下也是不安全的
         *会出现 ConcurrentModificationException 这个异常错误
         * 解决方案:
         * 2.Map map = Collections.synchronizedMap(new HashMap<>());
         * 3.Map map = new ConcurrentHashMap<>();
         * */
        Map<String,Object> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

二、Callable

快速了解JUC(二)_第1张图片
快速了解JUC(二)_第2张图片
快速了解JUC(二)_第3张图片
案例代码:

public class Test01 {
    public static void main(String[] args) throws ExecutionException , InterruptedException {
        /**
         * new Thread(这里要的是一个Runnable类型的,但是Callable不是那怎么启动MyThread的?)
         * 这个时候看文档会发现FutureTask这么个类,这个类也实现了Runnable所以可以把这个类传进去,
         * 然后在看FutureTask他的构造方法可以传一个Callable类型的,所以就说明了怎么启动的MyThread的一个过程
         *
         * */
//        new Thread(new FutureTask<>(new MyThread())).start();
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask<>(myThread);
        new Thread(futureTask,"A").start();
        String result = (String) futureTask.get(); //这个是一个阻塞的,一般放到最后
        System.out.println(result);
    }
}

class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "success";
    }
}

三、常用的辅助类CountDownLatch、CyclicBarrier、Semaphore

1.CountDownLatch

这个类是干嘛的呢?简单的说就是一个计数器用来负责减的,比如说下课放学,教室里一共有5个人,老师必须要等每个人都出去后才能关门,这时候没出去一个学生老师就会减一个知道,学生人数等于0,老师才关门。
示例代码如下所示:

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //一共是6个计数器
        CountDownLatch countDownLatch = new CountDownLatch(5);

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                System.out.println("开始减数");
                countDownLatch.countDown();//计数器减
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归零 然后执行下一步操作
        System.out.println("减数完成");

    }
}

快速了解JUC(二)_第4张图片

2.CyclicBarrier

简单了说这个类就是一个加数器,只有前面的加完了才能做最后一个是,像5个铠甲合体成为一个帝皇侠一样。具体代码示例如下:

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //这个可以把它看作一个加数器,但是jdk文档上不是这么说,上面提到了一个屏障的词,不过意思差不多
        //当加到5的时候就会去执行另一个操作,可以理解为5个普通的铠甲勇士合体为一个帝皇侠
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5,()->{
            System.out.println("合体帝皇侠成功");
        });

        for (int i = 1; i <= 5; i++) {
            int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "合体" + temp + "号铠甲");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf("线程" + i)).start();
        }

    }
}

快速了解JUC(二)_第5张图片

3.Semaphore(比较常用、重要)

这一般使用在限流里面,可以把它想成一个停车场,一次性只能进3辆车,而且每辆车只有通过acquire()拿到通行证才能进,出来时需要release()通行证,具体代码如下:

//Semaphore这个的意思是信号量
//acquire 只有在获得通行证才可以操作、 release  释放通行证
//类似于抢车位只有拿到了通行证才能进入,离开后需要释放通行证
public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到了通行证");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName() + "离开了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

快速了解JUC(二)_第6张图片

四、读写锁

接口ReadWriteLock、实现类ReentrantReadWriteLock。
读和读可以共存、读跟写不能共存、写跟写不能共存。具体代码如下所示:

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache2 cache = new MyCache2();
        //5个线程写
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.put("" + temp,"" + temp);
            },String.valueOf(i)).start();
        }

        //5个线程读
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.get("" + temp);
            },String.valueOf(i)).start();
        }
    }
}

//自定义缓存
class MyCache {
    private volatile Map<String,Object> map = new HashMap<>();

    public void put(String key,Object value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "写入完成" );
    }
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取完成");
    }
}

//自定义缓存2 加读写锁
class MyCache2 {
    private volatile Map<String,Object> map = new HashMap<>();
    //
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key,Object value) {
        try{
            readWriteLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "写入完成" );
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void get(String key) {
        try{
            readWriteLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完成");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            readWriteLock.readLock().unlock();
        }

    }
}

快速了解JUC(二)_第7张图片

五、阻塞队列(在线程池里很常见)

首先要理解什么是阻塞队列,说白了 首先他就是一个队列,但是为什么会带有阻塞这个词,其实可以这么理解,一个队列,当这个队列满了的时候,我们想要在往里塞东西时候,就得等待队列空,不然是放不进的这个时候就有阻塞,同理,取的时候如果队列空的话,就要等队列里有东西才能取。
快速了解JUC(二)_第8张图片

基本使用的方法:

快速了解JUC(二)_第9张图片
具体示例代码如下:

public class Test01 {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        //这种add、remove是会报异常的如果加不进去或者取不出来的时候
//        System.out.println(queue.add("a")); //true
//        System.out.println(queue.add("b"));
//        System.out.println(queue.add("c"));
//
//        System.out.println(queue.remove("a")); //true
//        System.out.println(queue.remove("b"));
//        System.out.println(queue.remove("c"));
//          System.out.println(queue.remove("d")); //异常

//              抛出异常	        特殊值	      阻塞	       超时
//        插入	add(e)	        offer(e)	  put(e)	   offer(e, time, unit)
//        移除	remove()	    poll()	      take()	   poll(time, unit)
//        检查	element()	    peek()	      不可用	       不可用

        System.out.println(queue.offer("a"));//true
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));

        System.out.println(queue.poll());// a
        System.out.println(queue.poll());// b
        System.out.println(queue.poll());// c
        System.out.println(queue.poll());//null



    }
}

六、SynchronousQueue同步队列

快速了解JUC(二)_第10张图片
具体示例代码如下:

//这个和其他的有一个区别就是只能放一个取一个只要放的那个不取出后面的就放不进去。
public class Test02 {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new SynchronousQueue<String>();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "放第一个");
                queue.put("first");
                System.out.println(Thread.currentThread().getName() + "放第二个");
                queue.put("second");
                System.out.println(Thread.currentThread().getName() + "放第三个");
                queue.put("third");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "取第一个");
                queue.take();
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "取第二个");
                queue.take();
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "取第三个");
                queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程2").start();
    }
}

效果图:
快速了解JUC(二)_第11张图片

七、线程池(重点)

说到线程池 就要想到一句话 三大方法、七个参数、四大策略

很常听说的一个名次池化技术,那什么叫池化技术呢?
池化技术:简单说就是,事先准备好一些资源,要用时就来拿,用完在还回来。
使用线程池的时候有一个注意点:
快速了解JUC(二)_第12张图片

(1)下面来先说下这个Executors的3个方法:

第一个、newSingleThreadExecutor()

快速了解JUC(二)_第13张图片

第二个、newFixedThreadPool(5)

快速了解JUC(二)_第14张图片

第三个、newFixedThreadPool(5)

快速了解JUC(二)_第15张图片

三个图片看完了会发现一个问题,这3个方法创建线程池的本质都是一样的都是通过ThreadPoolExecutor去实现的
下面就讲一下ThreadPoolExecutor里的7个参数到底是什么?

(2)ThreadPoolExecutor(args 。。。)七个参数

快速了解JUC(二)_第16张图片

 public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                              int maximumPoolSize,//最大线程池大小
                              long keepAliveTime, //规定的超过多少时间没人去管就释放
                              TimeUnit unit, //等待时间单位
                              BlockingQueue<Runnable> workQueue, //阻塞队列
                              ThreadFactory threadFactory,//线程工厂一般创建线程的,不用动
                              RejectedExecutionHandler handler //拒绝策略这是有4种的) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

说这七个参数前先来举个例子就能明白这7个参数什么意思。

假如说有一个银行,有5个窗口(这个对应最大线程数)=但是只有两个窗口是固定开着的(这个对应核心线程池数),还有3个窗口是会在人多的时候开,什么时候算人多呢,==当大厅的一个候客区3个座位(这个对应阻塞队列)都满了的时候这个时候就会触发,让那3个没开的窗口也开起来。==如果5个窗口都有人,并且候客区也满了,这个时候银行就会想一个办法去处理那个还想办理业务的人,这个时候就出现了4种策略
快速了解JUC(二)_第17张图片
具体示例代码如下:

public class Test02 {
    public static void main(String[] args) {
        ExecutorService service = new ThreadPoolExecutor(2,
                                5,
                                3,
                                TimeUnit.SECONDS,
                                new LinkedBlockingDeque<>(3),
                                Executors.defaultThreadFactory(),
                                new ThreadPoolExecutor.AbortPolicy() );
        try{
            for (int i = 1; i <= 10; i++) {
                //这里分析一波,假如说就5个人去办理业务,那3个人就在后客厅。
                //如果说有6个人去办理业务,那5个窗口就全打开,一个人在后客厅。
                //如果说有8个人去办理业务,5个人在5个窗口办理,3个人在后客厅。
                //如果说10个人,那超过8个的就拒绝你了
                service.execute(()->{
                    //这是有线程池去创建线程
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            service.shutdown();
        }


    }
}

继7个参数后的4个策略如下图所示:

快速了解JUC(二)_第18张图片
具体说明和解释如下代码所示:

//new ThreadPoolExecutor.AbortPolicy() 银行满了,还有人来,就不处理了,抛出异常
//new ThreadPoolExecutor.CallerRunsPolicy() 哪里来的去哪里,比如说从主线程来的那就让主线程去处理。
//new ThreadPoolExecutor.DiscardPolicy() 队列满了,直接丢掉任务不处理了,也不会抛出异常
//new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了,尝试去和最早的那个线程竞争,也不会出异常。
public class Test02 {
    public static void main(String[] args) {
        ExecutorService service = new ThreadPoolExecutor(2,
                                5,
                                3,
                                TimeUnit.SECONDS,
                                new LinkedBlockingDeque<>(3),
                                Executors.defaultThreadFactory(),
                                new ThreadPoolExecutor.DiscardOldestPolicy() );
        try{
            for (int i = 1; i <= 10; i++) {
                //这里分析一波,假如说就5个人去办理业务,那3个人就在后客厅。
                //如果说有6个人去办理业务,那5个窗口就全打开,一个人在后客厅。
                //如果说有8个人去办理业务,5个人在5个窗口办理,3个人在后客厅。
                //如果说10个人,那超过8个的就拒绝你了
                service.execute(()->{
                    //这是有线程池去创建线程
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            service.shutdown();
        }
    }
}

快速了解JUC(二)_第19张图片

最大线程数该如何定义?

定义分为两种情况
  • 1、CPU密集型 -》电脑是几核的,就是几
  • 2、IO密集型 -》判断程序中特别耗IO的线程

示例:
快速了解JUC(二)_第20张图片

你可能感兴趣的:(Java,并发编程,java)