JUC初体验

JUC基本知识点

所谓juc就是 java.util.concurrent 在并发编成中使用的工具包。
在这里插入图片描述

线程的状态:

  • 新建 (new) new一个线程对象
  • 就绪 (Runnable)调用线程的start方法,处于可运行状态,等待获取cpu使用权
  • 运行 (Running)执行程序代码
  • 阻塞 (Blocked) 放弃cpu的执行权
  • 销毁 (Dead)线程执行完或异常退出run方法,结束生命周期

阻塞分三种:

  1. 等待阻塞:运行的线程执行wait方法后,会释放占用资源,JVM把该线程放入“等待池”中。进入这个状态后,是不能自我唤醒的,需要其他线程唤醒(notify、notifyAll)。
  2. 同步阻塞:运行的线程在获取锁的对象时,发现锁已经被占用了,JVM会把该线程放入锁池中。
  3. 其他阻塞:运行线程的sleep、join或者IO请求阻塞。JVM会把该线程置为阻塞状态,当sleep超时、join等待终止或者超时、IO处理完毕,JVM会把该线程置为就绪状态

wait、sleep的区别

  1. sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。
  2. sleep不会释放琐,他也不需要占用锁。wait会释放锁,但调用它前提是当前线程占用琐(即代码要在synchronized中)。
  3. 它们都能被interrupted方法打断。

管程

就是monitor监视器,琐。是一种同步机制,保证同一时间只能有一个线程对保护数据或代码进行访问。
jvm同步基于进入和退出,使用管制对象实现。

用户线程、守护线程

用户线程:平时使用的线程或自定义的线程,主线程结束,用户线程运行,jvm存活。
守护线程:gc垃圾回收线程,运行在在后台,没有用户线程,都是守护线程,jvm结束。

lock接口

synchronized关键字

可以用来:修饰一个代码块、修饰一个方法(但是synchronized关键字不能被继承,子类重写父类的方法并不是同步的)、修饰一个静态方法、修饰一个类。

class Ticket {
    private int number = 30;

    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName()+" 卖出第" + (number--) + "张票" + ",剩余票的数量为" + number);
        }
    }
    public void method(){
        synchronized(Ticket.class){
            // todo
        }
    }
    public void methodA(){
        synchronized (this){
            // todo
        }
    }
}

synchronized主动释放锁的两种情况:

  • 获取琐的线程执行完该代码,然后释放对琐的占有
  • 线程执行发生异常,此时jvm会让线程自动释放琐

Lock

lock与synchronized的区别:

  • synchronized是关键字,Lock是一个接口
  • synchronized自动释放锁,Lock需要用户手动释放锁,不释放可能会导致死锁问题。
  • lock可以让等待琐的线程响应中断,synchronized不行。
  • lock可以知道是否成功获取锁
  • 在大量请求竞争下,lock性能远大于synchronized
class LTicket {
    private int number = 30;
    private final ReentrantLock lock = new ReentrantLock();

    public void Sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + " 卖出第" + (number--) + "张票,还剩" + number);
            }
        } finally {
            lock.unlock();
        }
    }
}

wait虚假唤醒

虚假唤醒:下面代码中wait在执行等待时,当被其它线程唤醒,那么它就不会再执行判断,二是接着向下执行。
出现线程阻塞等问题。解决方案if改用while,唤醒后执行判断。
wait在哪里睡,就在那里醒
在这里插入图片描述

    public synchronized void incr() throws InterruptedException {
        //判断 执行 通知
        if (number != 0) {
            this.wait(); //在哪里“睡”,就在那里“醒”
        }
        number++;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        //通知唤醒其它线程
        this.notifyAll();
    }
    public synchronized void decr() throws InterruptedException {
        if (number != 1) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        this.notifyAll();
    }

定制化通信

通过flag判断
在这里插入图片描述

class ShareResource{
//定制化标志
private int flag = 1;
private ReentrantLock reentrantLock = new ReentrantLock();

private Condition c1 = reentrantLock.newCondition();
private Condition c2 = reentrantLock.newCondition();
private Condition c3 = reentrantLock.newCondition();
    public void print5(int loop) throws InterruptedException {
        reentrantLock.lock();
        try {
            while(flag != 1){
                c1.await();
            }
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+ i + "轮数" + loop);
            }
            //通知
            flag = 2;
            c2.signal();
        } finally {
            reentrantLock.unlock();
        }
    }

    public void print10(int loop) throws InterruptedException {
        reentrantLock.lock();
        try {
            while(flag != 2){
                c2.await();
            }
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+ i + "轮数" + loop);
            }
            //通知
            flag = 3;
            c3.signal();
        } finally {
            reentrantLock.unlock();
        }
    }
    public void print15(int loop) throws InterruptedException {
        reentrantLock.lock();
        try {
            while(flag != 3){
                c3.await();
            }
            for (int i = 1; i < 16; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+ i + " 轮数" + loop);
            }
            //通知
            flag = 1;
            c1.signal();
        } finally {
            reentrantLock.unlock();
        }
    }
}
public class CustomNotice {
    public static void main(String[] args) {

        ShareResource shareResource = new ShareResource();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}

集合线程安全

ArrayList的add方法:并没有加锁,线程不安全
在这里插入图片描述
并发增加代码:注意这里是取数据的时候发生异常

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

并发修改异常
在这里插入图片描述

Vector解决

读写都加琐

List<String> list = new Vector<>();

在这里插入图片描述

增加关键字synchronized,解决线程安全问题,但是效率低下,使用少

Collections解决

List<String> list = Collections.synchronizedList(new ArrayList<>());

CopyOnWriteArrayList解决

List<String> list = new CopyOnWriteArrayList<>();

写式复制技术:只在写加锁
源码

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

获取原有数组和长度,
copy原数组,不过长度+1
将新数据填入复制数据,
最后将复制数组 赋值给原数组

hashSet

使用CopyOnWriteArraySet 解决
线程不安全:

        Set<String> set = new CopyOnWriteArraySet<>();
        // Set<String> set = new HashSet<>();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }

Hashset底层就是Hashmap,他是往map中放数据,所以key不能重复。(无序)

hashMap

使用 ConcurrentHashMap 解决

//        Map<String, String> map = new HashMap<>();
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 100; i++) {
            int key = i;
            new Thread(() -> {
                map.put(String.valueOf(key),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

多线程锁

悲观锁: 在获取资源数据后认为其它线程会修改该数据,因此在获取数据时会先加锁,确保数据不被修改。适合写操作多的场景。
乐观锁: 在获取资源数据后认为其它线程不会修改该数据,在java中是通过无锁编程实现,只是在更新数据时去判断数据是否被更改。如果被更改则会重试枪锁、放弃修改等操作。判断规则:1、版本号,2、CAS算法,Java原子类中递增操作就通过CAS实现。适合读操作多的场景。

synchronized同步代码块:实现使用的是monitorenter和monitorexit指令,一个enter两个exit(保证异常时也能释放锁)
synchronized普通同步方法:字节码中含有ACC_SYNCHRONIZED访问标识,执行线程会先持有monitor锁,再执行方法,最后无论正常或非正常完成都会释放锁。
synchronized静态同步方法:字节码含有ACC_STATIC,ACC_SYNCHRONIZED访问标识。

锁的8种情况

对象锁: synchronized 加在普通方法上锁的是调用此方法的对象(this),
类锁(Class): synchronized 加在静态方法上锁的是当前类的字节码中大Class
静态锁与类锁不会竞争锁,它俩锁的东西不一样。

class Phone{
    public static synchronized void sendMSM() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }
    public  synchronized void sendEmail() throws InterruptedException {
        System.out.println("------sendEmail");
    }
    public void getHello() {
        System.out.println("------getHello");
    }
}

/**
 * 1、标准访问  2、sendMSM停留4秒 3、通过同一对象调用getHello 4、创建两个phone对象 为了证明使用普通同步方法锁的是调用此方法的对象实例。
 *
 * 5、两个静态同步方法,两个线程使用一个对象调用两种方法  6、两个静态同步方法,两个线程使用两个对象调用两种方法 静态同步方法锁的是当前类(字节码中的大Class)
 *
 * 7、一个静态同步,一个普通同步,一个对象
 * 8、一个静态同步,一个普通同步,两个对象
 */

public class Lock_8 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        Phone phone1 = new Phone();

        new Thread(() -> {
            try {
                phone.sendMSM();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);
        new Thread(() -> {
            try {
                phone1.sendEmail();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"BB").start();
    }
}

公平锁与非公平锁

在new ReentrantLock()创建Lock对象时,使用无参构造就是非公平锁,有参构造(布尔类型传入true)就是公平锁。
公平锁:private final ReentrantLock lock = new ReentrantLock(true);
为什么会有公平锁/非公平锁?

  • 非公平锁能更充分利用CPU时间片,减少CPU空闲状态时间, 释放锁的线程更容易再次获取锁,减少了线程切换的开销效率高。
    源码:
    在这里插入图片描述

可重入锁(lock synchronized)

可重入锁我理解为,如家外面门的锁与家内房间的锁一致,拿到一把就可以畅通无阻。
下面代码如果synchronized不是可重复锁的话,那么会等到最外层同步代码块执行结束才能释放锁,产生死锁问题。

        Object o = new Object();
        new Thread(() -> {
            synchronized(o){
                System.out.println(Thread.currentThread().getName()+" 外锁");
                synchronized(o) {
                    System.out.println(Thread.currentThread().getName() + " 中锁");
                    synchronized(o) {
                        System.out.println(Thread.currentThread().getName() + " 内锁");
                    }
                }
            }
        },"t1").start();

每一个锁对象拥有一个锁计数器和一个指向持有该锁线程的指针。
重复获取锁有一个锁计数器,同一个对象获取(monitorenter)同一个锁时+1,离开(monitorexit)时-1,到0就完全释放锁。

lock重复获取锁时,一定要释放锁(上锁解锁)。否则其他线程无法获取这把锁(造成死锁现象)。

为什么任何一个对象都可以成为一个锁。

所有的类都继承Object类,java底层由c++支持,由objectMonitor.hpp文件控制。
_owner:锁定当前进程ID
_count:表示当前锁对象被加锁的次数(加锁+1,解锁-1),为0时未加锁。
_recursions:初始值为0,表示同步代码块重入的次数
_EntryList:阻塞队列,存放阻塞的线程
_WaitSet:等待队列,存放等待的线程

死锁

形成死锁四大必要条件:

  • 互斥条件
  • 占有且等待
  • 不可抢占(剥夺)
  • 循环等待

检测死锁:jps + jstack PID jconsole => 线程 => 检测死锁
两个或两个以上的线程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再执行下去。

产生死锁的原因:系统资源不足、进程运行推进顺序不合适、资源分配不当

        Object o1 = new Object();
        Object o2 = new Object();

        new Thread(() -> {
            synchronized (o1) {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                    synchronized (o2) {
                        System.out.println();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, "t1").start();
        new Thread(() -> {
            synchronized (o2) {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                    synchronized (o1) {
                        System.out.println();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t2").start();

Callable接口

Callable与Runnable相比较:它有返回值、异常抛出,它的执行方法是call()

在这里插入图片描述

class MyThread2 implements Callable {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"  come in Callable");
        return 200;
    }
}

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
        //lambda表达式
        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName()+"  come in Callable");
            return 1024;
        });

        new Thread(futureTask1,"AA").start();
        System.out.println(futureTask1.get()+" come over");
    }
}

juc辅助类

减少计数CountDownLatch

有参构造赋初始值,设定执行那一次任务后减一,直到初始值为0时,await方法停止等待。

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        
        CountDownLatch countDownLatch = new CountDownLatch(100);
        
        for (int i = 1; i <= 100; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 离开了");
                countDownLatch.countDown();
            },String.valueOf(i)).start();

        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+" 离开了");
    }
}

循环栅栏CyclicBarrier

允许一组线程相互等待,直到达到某个公共屏障点。到达某个条件就会执行CyclicBarrier的第二个参数的实现Runnable接口中的方法。

public class CyclicBarrierDemo {
    private static final int NUMBER = 7;
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
            System.out.println("集齐7颗龙珠召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+" 星被收集到了");
                    //执行等待
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

    }
}

信号灯Semaphore

一个计数信号量,PV操作。只有拿到许可的线程才能执行代码,未拿到的只能执行等待其他线程释放许可,如3个车位6辆车来停车。

public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆汽车
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    //获取许可证(抢占车位)
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+" 抢到了车位");

                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放许可证
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName()+ " ---------离开了车位");
                }
            },String.valueOf(i)).start();
        }
    }
}

读写锁

速度快于普通ReentrantLock
读写锁:一个资源可以被多个读线程访问,只能被一个写线程访问,不能同时存在读写操作,(不同线程)读写互斥,读读共享。

写锁饥饿:读操作没有完成时其他线程无法获取写锁。
锁降级:同一线程,在获取写锁完成写操作后再获取读锁进行去操作,释放写锁,然后现在就降级为读锁。

        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        lock.writeLock().lock();
        System.out.println("lock");
        //TODO
        lock.readLock().lock();
        System.out.println("read");

        lock.writeLock().unlock();
        //TODO
        lock.readLock().unlock();

写锁:独占锁,只允许一个线程执行操作
读锁:共享锁,允许多个线程同时执行

读写锁缺点: 1、造成(写)锁饥饿,一直读没有写操作。就是读操作占用大量资源,写操作无法进行(如高峰期坐地铁,却只有你一个人下车)。2、读的时候不能写,只有读完才能写.

  • 在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发
    现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
  • 在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写
    锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
  • 原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把
    获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了写
    锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释
    放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。

通过 new ReentrantReadWriteLock()实现读写锁

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    //读写锁
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    //写数据
    public void put(String key, Object value) {
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 正在执行写操作" + value);

            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key, value);

            System.out.println(Thread.currentThread().getName() + " 写完了" + value);
        } catch (InterruptedException e) {
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    //读数据
    public Object get(String key) {
        Object result = null;
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 正在执行读取操作" + key);

            TimeUnit.MILLISECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 读取完了" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
            return result;
        }
    }
}

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

        for (int i = 1; i <= 5; i++) {
            final String key = i + "", value = i + "";
            new Thread(() -> {
                myCache.put(key, value);
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final String key = i + "", value = i + "";
            new Thread(() -> {
                myCache.get(key);
            }, String.valueOf(i)).start();
        }
    }
}

StampedLock(版本锁,邮戳锁)

速度块于ReentrantLock,主要升级是读的过程中也允许锁的介入。
对于短的只读代码段,使用乐观模式通常可以减少征用提高吞吐量。
三种模式:

  1. Reading(悲观读模式),与ReentrantReadWriteLock的读锁类似。
  2. Writing(写模式),功能与ReentrantReadWriteLock的写锁类似。
  3. Optimistic reading(乐观读模式),无锁机制,类似于数据库中的乐观锁。支持读写并发,很乐观的认为读的时候没人修改,假如被修改升级到悲观读模式。

缺点:
4. 但不可重入
5. 悲观读锁和写锁不支持条件变量(condition),
6. 使用StampedLock不要调用interrupt()中断操作。

validate(long stamp)方法:返回true表示无修改邮戳。
tryOptimisticRead()方法:乐观读

很乐观的认为读的时候没人修改,假如被修改升级到悲观读模式。

    public void read() throws InterruptedException {
        long stamp = stampedLock.tryOptimisticRead();

        //TODO 模拟读操作
        System.out.println(Thread.currentThread().getName() + "\t" + "尝试读取");
        Thread.sleep(4000);
        long result = number.sum();

        if (!stampedLock.validate(stamp)) {//判断这4秒中是否有人修改这个值
            System.out.println(Thread.currentThread().getName()+"\t被修改");
            try {
                stamp = stampedLock.readLock();
                //TODO 模拟重新读取
                System.out.println(Thread.currentThread().getName() + "\t" + "尝试读取");
                result = map.get(version.sum() - 1);

                System.out.println("被修改\t"+Thread.currentThread().getName() + "\t" + "读取完成" + "\t" +result);
            }finally {
                stampedLock.unlockRead(stamp);
            }
        } else
            System.out.println("未被修改\t" + result);
    }

阻塞队列

在这里插入图片描述
当队列是空的,从队列中获取元素将会被阻塞。
当队列是满的,向队列中添加元素将会被阻塞。

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起
的线程又会自动被唤起

核心方法
第一组在使用时出现:队列满添加,队列空移除,队列空检查会抛异常。
第二组:成功返回true,失败返回false,检查空返回null
第三组:队列满添加、队列空移除会进入阻塞
第四组:和第三组一样,不过能设置阻塞时间

线程池ThreadPoll

基础知识点

线程池做的工作只要是控制运行的线程数量,处理过程中将任
务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,
超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

  • 优势:降低资源消耗、提高响应速度、提高线程可管理性

Executors.newFixedThreadPool(x) 创建x个线程
Executors.newSingleThreadExecutor() 创建单个线程
Executors.newCachedThreadPool() 创建可扩容的线程

使用:

        try{
            for (int i = 1; i <= 10; i++) {
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "办理业务");
                });
            }
        }finally {
            //关闭线程池,不关闭线程则不结束执行
            executorService.shutdown();
        }

三种创建方式的底层都是new ThreadPoolExecutor 对象
在这里插入图片描述
在这里插入图片描述
defaultHandler:默认拒绝策略

在这里插入图片描述
在execute()时才会创建线程

拒绝策略

超过最大线程数量、队列已经满了。

  • AbortPolicy(默认) :直接抛出RejectedExecutionException异常阻止系统正常运行
  • CallerRunsPolicy :”调用者运行“一种调节机制.该策略既不会抛弃任务,也不
    会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  • DiscardOldestPolicy :抛弃队列中等待最久的任务,然后把当前任务加人队列中
    尝试再次提交当前任务。
  • DiscardPolicy :该策略默默地丢弃无法处理的任务。不予任何处理也不抛出异常。
    如果允许任务丢失
    。这是最好的一种策略。

自定义线程池

实际开发中不用上面三种方式创建线程池,而是自定义。
原因:

  • FixedThreadPool和SingleThreadPool:允许请求队列的长度为 Integer.MAX_VALUE,面临大量请求会导致OOM。
  • CachedThreadPool 和 SingleThreadPool:允许创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM。
        //自定义线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,        //常驻线程数量
                5,   //最大线程数量
                2L,     //存活时间数量
                TimeUnit.SECONDS,     //存活时间单位
                new ArrayBlockingQueue<>(3),  //阻塞队列
                Executors.defaultThreadFactory(),     //创建线程的工厂
                new ThreadPoolExecutor.AbortPolicy()   //拒绝策略(这里是默认的抛异常)
        );
        
        //6个任务
        try{
            for (int i = 1; i <= 6; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "办理业务");
                });
            }
        }finally {
            //关闭线程池
            threadPool.shutdown();
        }

Fork/Jion 框架

分支合并框架
Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并

有点像归并排序

class MyTask extends RecursiveTask<Integer> {
    //拆分差值不能超过10,计算10以内的运算
    private static final int NUMBER = 10;
    private int begin; //拆分开始值
    private int end;   //拆分结束值
    private int result;//返回计算结果

    public MyTask(int begin,int end){
        this.begin =begin;
        this.end = end;
    }
    //拆分和合并的过程
    @Override
    protected Integer compute() {
        //判断两个数值差是否大于10
        if ((end - begin) <= NUMBER){
            for (int i = begin; i <= end; i++) {
                result += i;
            }
        }else { //进一步拆分
            //获取中间值
            int mid = begin + end >> 1;
            //拆分左边
            MyTask myTaskLeft = new MyTask(begin, mid);
            myTaskLeft.fork();
            //拆分右边
            MyTask myTaskRight = new MyTask(mid + 1, end);
            myTaskRight.fork();
            //合并结果
            result = myTaskLeft.join() + myTaskRight.join();
        }
        return result;
    }
}

public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyTask myTask = new MyTask(1, 100);
        //创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
        //得到结果
        int result = forkJoinTask.get();
        System.out.println(result);
        //关闭池对象
        forkJoinPool.shutdown();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值