JUC(二)

集合安全问题

List

使用List在多线程的情况下是不安全的里面的方法并没有加锁

public class ListTest {
    public static void main(String[] args) {
        /**
         * 下面三种方法解决
         * List<String> list = new Vector<>();
         * List<String> list = Collections.synchronizedList(new ArrayList<>());
         * List<String> list = new CopyOnWriteArrayList<>();
         */
        List<String> list = new ArrayList<>();

        // 用10个线程往list中add值,使用普通的ArrayList会报错:java.util.ConcurrentModificationException并发修改异常
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

1、Vector会在方法中使用synchronized关键字,效率比较低

public synchronized void addElement(E var1) {
    ++this.modCount;
    this.ensureCapacityHelper(this.elementCount + 1);
    this.elementData[this.elementCount++] = var1;
}

2、synchronizedList的许多方法使用synchronized加锁,但是遍历没有加锁,所以遍历要自己手动同步

public void add(int var1, E var2) {
    Object var3 = this.mutex;
    synchronized(this.mutex) {
        this.list.add(var1, var2);
    }
}

3、CopyOnWriteArrayList使用private transient volatile Object[] array;两个关键字和可重入锁,在写的时候复制,避免数据覆盖

public boolean add(E var1) {
    ReentrantLock var2 = this.lock;
    var2.lock();

    boolean var6;
    try {
        Object[] var3 = this.getArray();
        int var4 = var3.length;
        Object[] var5 = Arrays.copyOf(var3, var4 + 1);
        var5[var4] = var1;
        this.setArray(var5);
        var6 = true;
    } finally {
        var2.unlock();
    }

    return var6;
}

Set

public class SetTest {
    public static void main(String[] args) {
        /**
         * 下面两种方法解决
         * Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * Set<String> set = new CopyOnWriteArraySet<>();
         */
        Set<String> set = new HashSet<>();

        // 用20个线程往set中add值,使用普通的HashSet会报错:java.util.ConcurrentModificationException并发修改异常
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

HashMap

使用ConcurrentHashMap

Callable创建线程

  • 使用Callable创建线程,方法有返回值,可抛出异常

  • 在这里插入图片描述

  • 在这里插入图片描述

  • FutureTask

测试

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Thread和Runnable有关系,Callable要想和Thread扯上关系需要用到Runnable,Runnable有一个实现类FutureTask,FutureTask可以传入Callable对象
        MyThread myThread = new MyThread();
        FutureTask futureTask =  new FutureTask(myThread);
        // 有缓存
        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();
        // get方法可能有阻塞,要等call方法执行完
        String s = (String) futureTask.get();
        // 打印call方法返回值
        System.out.println(s);
    }
}

class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("耗时方法开始");
        TimeUnit.SECONDS.sleep(5);
        return "结果";
    }
}

CountDownLatch

  • 初始化给定线程数count
    在这里插入图片描述
  • countDown方法使count减1
  • await方法会等待count减到0时,执行后续代码,保证一些线程先完成

测试

public class CountDownTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(7);
        for (int i = 1; i <= 7; i++) {
            // 使lambda表达式可以获取i的值
            final int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + ":第" + temp + "个龙珠");
                // 计数器-1
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        // 等待7减到0
        countDownLatch.await();
        System.out.println("集齐龙珠");
    }
}

CyclicBarrier

  • 给定parties,当执行完parties个线程后执行指定线程barrierAction
    在这里插入图片描述
  • await方法,等待其它parties个线程执行完

测试

public class CyclicBarrierTest {
    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 {
                    // 等待parties+1(计数器+1),如果没加到会阻塞
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

Semaphore

可以限制线程的执行数
在这里插入图片描述

  • acquire阻塞
  • release释放

测试

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(4);
        for (int i = 0; i < 8; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName() + "被释放");
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

线程池

使用线程池的好处

  • 降低资源的消耗:可以重复利用已经创建好的线程,减少了线程创建和销毁造成的消耗
  • 提高响应速度:线程已经创建好,使用时少去了创建线程的时间消耗
  • 提高线程的可管理性:对线程进行统一的管理、调优和监控

三个方法

public class ThreeMethodTest {
    public static void main(String[] args) {
        // 单一线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 固定线程数的线程池
        ExecutorService executorService1 = Executors.newFixedThreadPool(5);
        // 自动伸缩的线层池
        ExecutorService executorService2 = Executors.newCachedThreadPool();

        try {
            for (int i = 0; i < 100; i++) {
                executorService1.execute(()->{
                    System.out.println(Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            executorService1.shutdown();
        }
    }
}

七个参数

public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
    this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
    this.mainLock = new ReentrantLock();
    this.workers = new HashSet();
    this.termination = this.mainLock.newCondition();
    if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
        if (var6 != null && var7 != null && var8 != null) {
            this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
            this.corePoolSize = var1;
            this.maximumPoolSize = var2;
            this.workQueue = var6;
            this.keepAliveTime = var5.toNanos(var3);
            this.threadFactory = var7;
            this.handler = var8;
        } else {
            throw new NullPointerException();
        }
   	} else {
        throw new IllegalArgumentException();
    }
}
  • corePoolSize核心线程池大小
  • maximumPoolSize最大线程池大小
  • keepAliveTime非核心线程空闲,存活时间
  • unit时间单位
  • workQueue阻塞队列
  • threadFactory创建线程的线程工厂
  • handler拒绝策略
    • AbortPolicy抛弃任务,抛出异常
    • CallerRunsPolicy拒绝后由提交该任务的线程执行被拒绝的任务
    • DiscardOldestPolicy抛弃最早任务,重新提交被拒绝的任务
    • DiscardPolicy抛弃任务,不抛出异常

使用Executors创建newCachedThreadPool时最大线程数约为21亿,会造成OOM,所以推荐使用ThreadPoolExecutor创建线程池

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    }

使用ThreadPoolExecutor创建线程池

public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,  // 核心线程池大小
                5,  // 最大线程池
                5,  // 超时时间
                TimeUnit.SECONDS,   // 超时时间单位
                new LinkedBlockingDeque<>(3),   // 阻塞队列(容量)
                Executors.defaultThreadFactory(),   // 默认线程工厂
                new ThreadPoolExecutor.AbortPolicy()    // 拒绝策略
        );
        try {
            // 最大线程数5+3=8
            for (int i = 0; i < 8; i++) {
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            threadPoolExecutor.shutdown();
        }
    }
}

当用满2个核心线程,接下来的任务会进入阻塞队列容量为3,二者都满了之后还有任务进入,则会启用剩下的线程直到最大线程数,超过之后则会通过拒绝策略采取措施

如何确定maximumPoolSize的大小:

  • CPU密集型:使用Runtime.getRuntime().availableProcessors()获取可利用核心数,即CPU的核数
  • I/O密集型:判断消耗大量IO资源的任务数,给出至少等于此的最大线程池,一般给两倍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值