JUC 并发编程学习(2)

1. ArrayList 多线程不安全


在多条线程下,对同一个ArrayList执行add方法时,可能会报java.util.ConcurrentModificationException,即为并发修改时错误。解决方法有3种:

  1. List list = new Vector<>();
  2. List list = Collections.synchronizedList(new ArrayList<>());
  3. List list = new CopyOnWriteArrayList<>();
面试问题:使用CopyOnWriteArrayList,不使用Vector的原因?

Vector 使用的是 synchronizeid 效率比较低
CopyOnWriteArrayList 使用的是 lock锁 效率比较高

package com.mango.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class Test1 {
    public static void main(String[] args) {

        List<Object> list = Collections.synchronizedList(new ArrayList<>());
        for(int i = 0;i < 100;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

2. Set 多线程不安全


在多条线程下,对同一个HashSet执行add方法时,可能会报java.util.ConcurrentModificationException,即为并发修改时错误。解决方法有2种:

  1. Set< String > set = Collections.synchronizedSet(new HashSet<>());
  2. Set< String > set = new CopyOnWriteArraySet<>();
package com.mango.unsafe;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

public class Test2 {
    public static void main(String[] args) {
//        HashSet<String> set = new HashSet<>();
//        Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 10 ; i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

3. 关于Map

在这里插入图片描述
在多条线程下,对同一个HashMap执行put方法时,可能会报java.util.ConcurrentModificationException,即为并发修改时错误。解决方法有2种:

  1. Set< String > set = Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
  2. Set< String > set = new ConcurrentHashMap<>();
package com.mango.unsafe;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class Test3 {
    public static void main(String[] args) {
        // map 是这样的嘛?不是,工作中不用HashMap
        // 默认等价于什么? new HashMap<>(16,0.75);

        Map<Object, Object> map = new ConcurrentHashMap<>();
//        Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
        // 加载因子,初始化容量
        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();
        }
    }
}

4. 关于Callable

  1. 可以有返回值。
  2. 可以抛出异常。
  3. 方法不同。run() / call()
package com.mango.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> stringFutureTask = new FutureTask<String>(new MyThread());
        new Thread(stringFutureTask,"A").start(); // 怎么启动callable
        String s = stringFutureTask.get();
        System.out.println(s);
    }
}
class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "1024";
    }
}

new Thread无法直接执行Callable 所以需要一个Runnable的父级FutureTask,作为一个适配器,这个类需要一个Callable实现类作为参数。

5. 常用的辅助类

5.1 CountDownLatch

countDownLatch.countDown(); 计数器 -1。
countDownLatch.await(); 等待计数器归零,然后向下执行。

每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await();就会被唤醒。

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 倒计时 总数是6
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"GO OUT");
                countDownLatch.countDown(); // 数量-1
            },String.valueOf(i)).start();
        }
        countDownLatch.await(); // 等待计数器归零,然后再向下执行
        System.out.println(1);
    }
}

5.2 CyclicBarrier

加法计数器

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功");
        });
        for (int i = 1; i <= 7; i++) {
            final 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();
                }
            }).start();
        }
    }
}

5.3 Semaphore

semaphore.acquire(); 获得,假设已经满了,等待,等到被释放为止!
semaphore.release(); 释放,会将当前的信号量+1,然后唤醒等待线程!

作用:多个共享资源互斥的作用!并发限流,控制最大的线程数量!


public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量:停车位!限流
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                // semaphore.acquire();
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }

                // semaphore.release(); 释放
            },String.valueOf(i)).start();
        }
    }
}

6. ReentrantReadWriteLock

独占锁(写锁):一次只能被一个线程占有
共享锁(读锁):多个线程可以同时占有
读-读 可以共存!
读-写 不能共存!
写-写 不能共存!
ReentrantReadWriteLock 可以使写的时候只能一条线程写,读的时候一起读,如果使用lock,读的时候也只能一个一个线程读。

package com.mango.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test {
    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();

        // 写入
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        // 读取
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.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);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取结束");
    }
}

// 加锁
class MyCache2{
    private volatile Map<String,Object> map = new HashMap<>();

    // 读写锁:更加细粒度的控制
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    // 存,写,写入的时候,只希望同时只有一个线程写
    public void put(String key,Object value){
        lock.writeLock().lock();
        System.out.println(Thread.currentThread().getName()+"写入"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"结束");
        lock.writeLock().unlock();
    }

    // 取,读,所有人都可以读
    public void get(String key){
        lock.readLock().lock();

        System.out.println(Thread.currentThread().getName()+"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取结束");
        lock.readLock().unlock();
    }
}

7. 阻塞队列


7.1 阻塞

写入:如果队列满了,就必须阻塞等待。

7.2 队列

取:如果队列是用空,必须阻塞等待生产。

7.3 阻塞队列

是Collection的子类,和List、Set同级。
BlockingQueue不是新的东西。
在这里插入图片描述
什么情况下我们会使用阻塞队列:多线程并发处理、线程池!
在这里插入图片描述

7.4 四组API

(1)ArrayBlockingQueue

方式抛出异常有返回值,不抛出异常阻塞等待超时等待
添加addofferputoffer( , , )
移除removepolltakepoll( , )
判断队首元素elementpeek--

两种队列异常:IllegalStateExceptionNoSuchElementException

(2)SynchronousQueue

SynchronousQueue 是同步队列,和其他的BlockingQueue不一样,SynchronousQueue 不存储元素,put了一个元素,必须从里面先take取出来。

public class Test02 {
    public static void main(String[] args) {
        SynchronousQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName()+"put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName()+"put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t2").start();
    }
}

8. 线程池(重点)


三大方法、7大参数、4种拒绝策略

池化奇数

程序的运行,本质:占用系统资源!优化资源的使用!=》池化奇数

线程池、连接池(JDBC)、内存池、对象池…创建、销毁。十分浪费资源

池化技术:实现准备好一些资源,有人要用,就来我这里拿,用完之后还给我,下个人再来用。

好处:

  • 降低资源消耗。
  • 提高响应的速度。
  • 方便管理。

线程复用、可以控制最大并发数、管理线程

8.1 三大方法

Executors 工具类中的三大方法。
在这里插入图片描述

(1)Executors.newSingleThreadExecutor();

单个线程。

// 使用线程池之后,使用线程池创建
public class Test1 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
//        Executors.newFixedThreadPool(5);
//        Executors.newCachedThreadPool();

       try{
           for (int i = 0; i < 10; i++) {
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"ok");
               });
           }
       }catch(Exception ee) {
           ee.printStackTrace();
       }finally {
           threadPool.shutdown();
       }

        // 线程池用完要关闭
    }
}

(2)Executors.newFixedThreadPool(5);

创建一个固定的线程池大小。

// 使用线程池之后,使用线程池创建
public class Test1 {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
//        Executors.newCachedThreadPool();

       try{
           for (int i = 0; i < 10; i++) {
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"ok");
               });
           }
       }catch(Exception ee) {
           ee.printStackTrace();
       }finally {
           threadPool.shutdown();
       }

        // 线程池用完要关闭
    }
}

(3) Executors.newCachedThreadPool();

可伸缩的,遇强则强,遇弱则弱。

// 使用线程池之后,使用线程池创建
public class Test1 {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        ExecutorService threadPool = Executors.newCachedThreadPool();

       try{
           for (int i = 0; i < 10; i++) {
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"ok");
               });
           }
       }catch(Exception ee) {
           ee.printStackTrace();
       }finally {
           threadPool.shutdown();
       }
        // 线程池用完要关闭
    }
}

8.2 七大参数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本质:ThreadPoolExecutor ();

 public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
                              int maximumPoolSize, // 最大线程池大小
                              long keepAliveTime,  // 超时了没有人调用,自动释放
                              TimeUnit unit,  // 超时单位
                              BlockingQueue<Runnable> workQueue, // 阻塞队列
                              ThreadFactory threadFactory, // 线程工厂:创建线程,一般不用动
                              RejectedExecutionHandler handler) { // 拒绝策略
        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;
    }
   

8.3 四种拒绝策略

在这里插入图片描述

(1) new ThreadPoolExecutor.AbortPolicy()

银行满了 候客区也满了 还有人进来 不处理这个人的,抛出异常 RejectedExecutionException

会报错

 
// 使用线程池之后,使用线程池创建
public class Test1 {
    public static void main(String[] args) {
        // 自定义线程池!
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() 
                );

       try{
           for (int i = 1; i <= 10; i++) {
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"ok");
               });
           }
       }catch(Exception ee) {
           ee.printStackTrace();
       }finally {
           threadPool.shutdown();
       }

        // 线程池用完要关闭
    }
}

(2) new ThreadPoolExecutor.CallerRunsPolicy()

哪来的去哪里

假如线程池超过了最大值(阻塞队列+最大线程池的值),会回到main方法调用。

不会报错

public class Test1 {
    public static void main(String[] args) {
        // 自定义线程池!
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()  // 哪里来的去哪里
                );

       try{
           for (int i = 1; i <= 10; i++) {
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"ok");
               });
           }
       }catch(Exception ee) {
           ee.printStackTrace();
       }finally {
           threadPool.shutdown();
       }

        // 线程池用完要关闭
    }
}

(3) new ThreadPoolExecutor.DiscardPolicy()

队列满了不继续执行任务

假如线程池超过了最大值(阻塞队列+最大线程池的值),会回到main方法调用。

不会报错

public class Test1 {
    public static void main(String[] args) {
        // 自定义线程池!
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy()  // 队列满了
                );

       try{
           for (int i = 1; i <= 10; i++) {
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"ok");
               });
           }
       }catch(Exception ee) {
           ee.printStackTrace();
       }finally {
           threadPool.shutdown();
       }

        // 线程池用完要关闭
    }
}

(4) new ThreadPoolExecutor.DiscardOldestPolicy()

队列满了尝试去和最早竞争

假如线程池超过了最大值(阻塞队列+最大线程池的值),会回到main方法调用。

不会报错

public class Test1 {
    public static void main(String[] args) {
        // 自定义线程池!
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()  // 队列满了
                );

       try{
           for (int i = 1; i <= 10; i++) {
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"ok");
               });
           }
       }catch(Exception ee) {
           ee.printStackTrace();
       }finally {
           threadPool.shutdown();
       }

        // 线程池用完要关闭
    }
}

9. 小结

最大线程该如何定义?

9.1 CPU 密集型

几核就是几,可以保持CPU的效率最高!
获取CPU的方法Runtime.getRuntime().availableProcessors()

9.2 IO 密集型

判断你程序中十分耗IO的线程。
程序 15个大型任务 io十分占用资源!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值