多线程中级

容器

  • 容器的示意图

在这里插入图片描述

CopyOnWriteArrayList

  • 下面我们来描述下List下的CopyOnWriteArrayList

  • 简介

是一种用于程序设计中的优化策略,当某个人要修改这个内容的时候,才会真正把内容copy出去形成新的内容然后在改。

  • 作用是什么

CopyOnWrite容器即写时复制容器。通俗的理解当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将容器进行Copy复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器并发的读,而不需要加锁。因为当前容器不会添加任何元素。

  • 应用的场景

写的时候添加锁,读的时候不添加锁 读多写少的时候使用

  • 案例:
public class CopyOnWriteList {
    public static void main(String[] args) {

        List<String> lists = new CopyOnWriteArrayList<String>();
        Random r = new Random();
        Thread[] threads = new Thread[100];

        for(int i=0; i<threads.length; i++){
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    for(int i=0; i<1000;i++){
                        lists.add("a"+r.nextInt(10000));
                    }
                }
            };

            threads[i]= new Thread(runnable);
        }

        runAndComputeTime(threads);

        System.out.println(lists.size());

    }

    private static void runAndComputeTime(Thread[] threads) {
        long start = System.currentTimeMillis();

        Arrays.asList(threads).forEach(t->t.start());
        Arrays.asList(threads).forEach(t-> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.getMessage();
            }
        });

        long end = System.currentTimeMillis();
        System.out.println("消耗的时间为:"+(end-start));
    }
}
  • 来说说Collecttion下的Queue
public interface Queue<E> extends Collection<E> {
    boolean add(E var1);

    boolean offer(E var1); //相当于list中的add,添加成功返回true

    E remove();

    E poll();  //取完值并且remove移除掉

    E element();
 
    E peek(); //取完值不会remove掉
}

LinkedBlockingQueue

  • 知识点一
  • LinkedBlockingQueue

内部由单链表实现,只能从头部获取元素,从尾部添加元素。添加元素和获取元素都有独立的锁,是读写分离的。采用可重入锁来保证并发的情况下线程安全。

  • LinkedBlockingQueue一共由三个构造器,无参构造器,指定容量的构造器,可以传入容器的构造器,如果为无参的构造器可能会出现内存溢出的现象。
 public LinkedBlockingQueue()//设置容量为Integer.MAX

 public LinkedBlockingQueue(int capacity)//设置指定容量
 
 public LinkedBlockingQueue(Collection<? extends E> c)//穿入一个容器,如果调用该构造器,容量默认也是Integer.MAX_VALUE
  • 取数据
    take():首选。当队列为空时阻塞。
    poll():弹出列顶元素,队列为空时,返回空。
    peek():返回列顶元素,但元素不弹出。队列为空的时候,返回null。
    remove():移除某个元素,队列为空的时候抛出异常,成功返回true。

  • 添加元素
    put():首选。队满是阻塞
    offer():队满返回false

  • 案例

public class LinkedBlockingQueueDemo {


    static BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();


    public static void main(String[] args) {

        new Thread(()->{
            for(int i=0;i<100;i++){
                try {
                    blockingQueue.put("a"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"p1").start();



        //起了五个线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (;;) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " take -" + blockingQueue.take()); //如果空了,就会等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "c" + i).start();

        }

    }
}

  • 执行结果
....
c3 take -a99
c1 take -a87
c0 take -a86
c2 take -a91

synchronizedMap与correntHashMap

  • 接下来我们着重的来说说Map中的并发,Map中HashMap在处理并发的问题是不安全的,而HashTable虽然是安全的,但是效率偏低。而 synchronizedMap和correntHashMap是采用CAS锁机制结构,能大幅度提升性能

  • 案例

public class SynchronizedHashMap {

    //修改相应的Map类型即可
    static Map<UUID, UUID> m = Collections.synchronizedMap(new HashMap<>());

    static int count = 1000000;
    static UUID[] keys = new UUID[count];
    static UUID[] values = new UUID[count];
    static final int THREAD_COUNT = 100;

    static {
        for (int i = 0; i < count; i++) {
            keys[i] = UUID.randomUUID();
            values[i] = UUID.randomUUID();
        }
    }

    static class MyThread extends Thread {
        int start;
        int gap = count / THREAD_COUNT;

        public MyThread(int start) {
            this.start = start;
        }

        @Override
        public void run() {
            for (int i = start; i < start + gap; i++) {
                m.put(keys[i], values[i]);
            }
        }
    }

    public static void main(String[] args) {

        long start = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];

        for (int i = 0; i < threads.length; i++) {
            threads[i] =new MyThread(i * (count / THREAD_COUNT));
        }

        for (Thread t : threads) {
            t.start();
        }

        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        long end = System.currentTimeMillis();
        System.out.println(end - start);

        System.out.println(m.size());

        //-----------------------------------

        start = System.currentTimeMillis();
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000000; j++) {
                    m.get(keys[10]);
                }
            });
        }

        for (Thread t : threads) {
            t.start();
        }

        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}
  • 运行结果
490
1000000
53443

Executor

  • Executor框架的成员及其关系可以用一下的关系图表示:
    在这里插入图片描述
    在这里插入图片描述
  • 那么在我们开发中一般使用ThreadPoolExecutor类,下面带大家来认识下核心的几大参数
corePoolSize:核心线程
maxPoolSize:最大线程数
keepAliveTime:存活时间
TimeUnit:时间单位
BlockingQueen:任务队列
ThreadFactory:线程工厂
RejectStratege:拒绝策略
   - Absort
   - DisCard
   - DisCardOld
   - CallsRuns:谁提交的教给谁处理

ThreadPoolExecutor

  • 接下来我们来简单的看看如何使用ThreadPoolExecutor

  • 案例演示

public class ThreadExecutorPoolDemo {

    //定义一个Runnable
    static class Task implements Runnable {

        private int i;

        public Task(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " Task " + i);
            try {
                System.in.read();//阻塞
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "Task{" +
                    "i=" + i +
                    '}';
        }
    }

    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,
                                                             4,
                                                               60,
                                                              TimeUnit.SECONDS,
                                                              new ArrayBlockingQueue<>(4),
                                                              new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 0; i < 8; i++) {
            //定义为8个的原因是,最大线程数为4 + 队列最大可以存4个 为八个 超过八个执行拒绝策略
            poolExecutor.execute(new Task(i));
        }

        System.out.println("在队列中等待的线程数:"+poolExecutor.getQueue());

        //测试 当队列都满了 这个教给main方法自己处理
        poolExecutor.execute(new Task(100));

        System.out.println("main在队列中等待的线程数:"+poolExecutor.getQueue());

        boolean shutdown = poolExecutor.isShutdown();
        System.out.println(shutdown);
        poolExecutor.shutdown();

    }
}
  • 执行结果
在队列中等待的线程数:[Task{i=2}, Task{i=3}, Task{i=4}, Task{i=5}]
main在队列中等待的线程数:[Task{i=3}, Task{i=4}, Task{i=5}, Task{i=100}]
false
pool-1-thread-1 Task 0
pool-1-thread-2 Task 1
pool-1-thread-3 Task 6
pool-1-thread-4 Task 7

Futrue和FutrueTask

  • Futrue接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行结果进行获取(get()),取消(cancle()),判断是否完成等操作。
public interface Future<V> {  
        boolean cancel(boolean mayInterruptIfRunning);  
        boolean isCancelled();  
        boolean isDone();  
        V get() throws InterruptedException, ExecutionException;  
        V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;  
} 

方法解析:

  • V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成

  • V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。

  • boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true

  • boolean isCanceller() :如果任务完成前被取消,则返回true。

  • boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(…)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(…)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。

  • 总结
    通过方法分析我们也知道实际上Future提供了3种功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后额结果。

但是我们必须明白Futrue只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask登场。
在这里插入图片描述

  • 分析
    FutureTask除了实现了Futrue还实现了Runnable接口(即可以通过Runnable接口实现线程,也可以通过Futrue获取线程执行后的结果)。因此Futrue也可以直接交给Exectors执行。
public class CallableDemo implements Callable<Integer> {

    private int sum;

    @Override
    public Integer call() throws Exception {
        System.out.println("Callable子线程开始计算啦!");
        Thread.sleep(2000);

        for (int i = 0; i < 5000; i++) {
            sum = sum + i;
        }

        System.out.println("Callable子线程计算结束!");
        return sum;
    }

    public static void main(String[] args) {
        //创建线程池
        ExecutorService es = Executors.newSingleThreadExecutor();
        //创建Callable对象任务
        CallableDemo calTask = new CallableDemo();
        //使用FutureTask
        FutureTask<Integer> futureTask = new FutureTask(calTask);
        //提交任务并获取执行结果
        //或者使用future方法
        //Future<Integer> future = es.submit(calTask);
        es.submit(futureTask);
        //关闭线程池
        es.shutdown();
        try {
            Thread.sleep(2000);
            System.out.println("主线程在执行其他任务");

            if (futureTask.get() != null) {
                //输出获取到的结果
                System.out.println("future.get()-->" + futureTask.get());
            } else {
                //输出获取到的结果
                System.out.println("future.get()未获取到结果");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("主线程在执行完成");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值