JUC高并发编程


一、idea环境搭建

二、 多线程知识回顾

  • 并行与并发的概念

并行就是多核CPU同时执行线程;
并发是适用于单核CPU,快速的轮转线程

public class Test{
	public static void main(String[] args){
	//获取CPU核数 一般都是12核
		System.out.println(Runtime.getRuntime().availableProcessors());
	}
}
  • 面试题:java能操作线程吗?

不能,因为启动线程的是start();而看start()的源码可以发现,线程被装进了一个组groud;然后被start0()这个方法执行;而start0()这个是在JVM的本地方法区内的,是C++写的;是与硬件相关的;所以,java不能操作线程,只能调用start()去启动

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
         //把启动的线程装进这个组内groud
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                //线程启动失败
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
	//JVM中本地方法区内的方法 被native修饰
    private native void start0();
  • 面试题:线程的状态有几个?

6种 | 直接看源码 | 枚举 |Thread.state

public enum State {
       //new状态 新生
        NEW,

       //可运行状态 一种复合状态 准备运行|正在运行 由JVM决定
        RUNNABLE,

       //阻塞
        BLOCKED,

       //等待,死等: 没其他线程唤醒就会一直等下去
        WAITING,

       //超时等待  有等待时间,时间一到,就不等了
        TIMED_WAITING,

       //终止
        TERMINATED;
    }

  • 面试题:wait()与sleep()的区别

1.wait来自Object类;sleep来自Thread类
2.wait会释放锁对象;sleep不会释放锁对象
3.wait必须在同步代码块中使用;sleep可以随意使用
4.wait不需要捕获异常;sleep必须要捕获异常
5.一般不写sleep(),而是写TimeUnit.DAYS.sleep(1)*TimeUnit.SECONFD.sleep(2)*

  • 传统的多线程并发的去访问统一资源
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
			//自定义锁
        Object lock = new Object();

        //定义同一资源
        Bank bank = new Bank();
        //创建线程
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    bank.draw();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"李四").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    bank.draw();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            },"王五").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    synchronized (lock){
                        bank.save();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            },"行老板").start();
    }
}
//定义一个银行类 Bank类 完全与线程无关 解耦合 一种认可的编码方式
class Bank{
    //金额
    private double money = 9000;

    //取钱
    public synchronized void draw() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
        if (money <= 300){
            System.out.println(Thread.currentThread().getName()+"还剩"+money+"不够取了");
        }else {
            this.money = money-300;
            System.out.println(Thread.currentThread().getName()+"取了300块,还剩"+money);
        }
    }
    public  void save() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        this.money = money+600;
        System.out.println(Thread.currentThread().getName()+"存了600块,还剩"+money);
    }
}

在这里插入图片描述

三、lock锁 深度解析

  • 先看JAVA-API文档
    在这里插入图片描述

  • Lock锁 源码分析实现类ReentrantLock()

Lock lock = new ReentrantLock();

		//new NonfairSync() 表示一个非公平锁 JVM采用的是 非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
		//传入一个Boolean值   new FairSync() 表示公平锁  |  new NonfairSync() 非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平锁与非公平锁的区别

公平锁:多个线程排好队,一个一个来占用锁对象;效率低
非公平锁:当A线程一抢占CPU时,就会去尝试占用锁对象;若没有占用到锁对象,就会去排队;占到了就用;所以,当多线程访问同一资源时,有时候是线程A占用锁对象;有时候是线程B占用锁对象;无序的一种状态
这里要分清楚:占用CPU资源 与 抢占锁对象;这是两个概念;当一个线程去占用CPU资源后,才有资格去抢占锁对象

  • 使用Lock锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test01 {
    public static void main(String[] args) {
        //同一资源对象
        Costs cost = new Costs();

        new Thread(()->{
            for (int i = 0; i < 15; i++) {
                try {
                    cost.eat();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"大吃货").start();

        new Thread(()->{
            for (int i = 0; i < 15; i++) {
                cost.doBun();
            }
        },"男老板").start();

        new Thread(()->{
            for (int i = 0; i < 15; i++) {
                cost.doBun();
            }
        },"女老板").start();
    }

}
//定义包子铺
class Costs{
    //包子
    private int bun = 10;

    //lock锁
    Lock lock = new ReentrantLock();

    //做包子
    public void doBun() {
        //加锁
        lock.lock();
        try{
            if (bun >= 30){
                System.out.println(Thread.currentThread().getName() + "说“包子有30个了,我先睡四秒再做”");
                TimeUnit.SECONDS.sleep(4);
            }else {
                this.bun +=3;
                System.out.println(Thread.currentThread().getName() + "-->做了三个包子,现在有"+bun);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //解锁
            lock.unlock();
        }
    }
    //吃包子
    public synchronized void eat() throws InterruptedException {
        if (bun <= 5){
            System.out.println(Thread.currentThread().getName()+"包子只有"+bun+"个,不够我吃;我先睡三秒");
            TimeUnit.SECONDS.sleep(3);
        }else {
            this.bun -= 5;
            System.out.println(Thread.currentThread().getName()+"吃了5个包子,还剩"+bun);
        }
    }
}

四、lock锁-condition.await() | condition.signalAll()

查看java-API文档
在这里插入图片描述
先来回顾一下synchronize版的消费者与生产者问题

import java.util.concurrent.TimeUnit;

public class Test02 {
    public static void main(String[] args) {
        Candy candy = new Candy();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    candy.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"老板").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    candy.minus();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"顾客1").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    candy.minus();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"顾客2").start();
    }
}
class Candy{

    private int sweets = 10;

    //+3
    public synchronized void add() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        if (this.sweets >= 20){
            System.out.println(Thread.currentThread().getName()+"==> 糖果够了,我先休息一下");
            this.wait();
        }else{
            this.sweets+=3;
            System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
            this.notifyAll();
        }
    }
    //-1
    public synchronized void minus() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        if (this.sweets <= 3){
            System.out.println(Thread.currentThread().getName()+"==> 糖果不够吃了,我先休息一下");
            wait();
        }else{
            this.sweets--;
            System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
            this.notifyAll();
        }
    }
}

使用Lock锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test02 {
    public static void main(String[] args) {
        Candy candy = new Candy();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    candy.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"老板").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    candy.minus();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"顾客1").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    candy.minus();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"顾客2").start();
    }
}
class Candy{

    private int sweets = 10;
    //lock锁
    Lock lock = new ReentrantLock();
    //Condition对象
    Condition connection01 = lock.newCondition();

    Condition connection02 = lock.newCondition();

    //+3
    public  void add() throws InterruptedException {
        try {
            lock.lock();
            TimeUnit.SECONDS.sleep(2);
            if (this.sweets >= 20){
                System.out.println(Thread.currentThread().getName()+"==> 糖果够了,我先休息一下");
                connection01.await();
            }else{
                this.sweets+=3;
                System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
                connection01.signal();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //-1
    public  void minus() throws InterruptedException {
        try {
            lock.lock();
            TimeUnit.SECONDS.sleep(2);
            if (this.sweets <= 3){
                System.out.println(Thread.currentThread().getName()+"==> 糖果不够吃了,我先休息一下");
                connection02.await();
            }else{
                this.sweets--;
                System.out.println(Thread.currentThread().getName()+"==> 糖果"+sweets);
                connection02.signal();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

我在上面代码写了两个Condition对象;可以试着比较一下一个Condition对象与两个Contition对象的区别
lock锁实现有序的精准唤醒

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test03 {

    public static void main(String[] args) {
        LineUp test = new LineUp();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    test.one();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"1号学生").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    test.tow();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }},"2号学生").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    test.three();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"3号学生").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    test.fore();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"4号学生").start();
    }
}

class LineUp{
    Lock lock = new ReentrantLock();

    Condition one = lock.newCondition();
    Condition tow = lock.newCondition();
    Condition three = lock.newCondition();
    Condition fore = lock.newCondition();

    int number = 1;

    public void one() throws InterruptedException {
        lock.lock();
        TimeUnit.SECONDS.sleep(1);
        try {
            while(number != 1){
                //持有one这个condition对象的线程等待
                one.await();
            }
            System.out.println(Thread.currentThread().getName());
            number = 2;
//            唤醒持有tow这个condition对象的线程
            tow.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
             lock.unlock();
        }
    }
    public void tow() throws InterruptedException {
        lock.lock();
        TimeUnit.SECONDS.sleep(1);
        try {
            while(number != 2){
                tow.await();
            }
            System.out.println(Thread.currentThread().getName());
            number = 3;
            three.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void three() throws InterruptedException {
        lock.lock();
        TimeUnit.SECONDS.sleep(1);
        try {
            while(number != 3){
                three.await();
            }
            System.out.println(Thread.currentThread().getName());
            number = 4;
            fore.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void fore() throws InterruptedException {
        lock.lock();
        TimeUnit.SECONDS.sleep(1);
        try {
            while(number != 4){
                fore.await();
            }
            System.out.println(Thread.currentThread().getName());
            number = 1;
            one.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

五、锁是什么?

三锁现象

1.同一资源的两个普通的同步方法
public class Test04 {
    public static void main(String[] args) {
        //同一资源
        One one = new One();

        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                one.first();
            }
        },"一号").start();

        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                one.second();
            }
        },"二号").start();
    }

}

class One{

    public synchronized  void first(){
        System.out.println(Thread.currentThread().getName());
    }

    public synchronized  void second(){
        System.out.println(Thread.currentThread().getName());
    }
}

上述代码,不用想,肯定是先执行“1号”,再执行“2号”;为什么是这个现象呢?
观点一:一号线程先执行 | 没错,但是不够全面
观点二: 一号线程先抢占了锁 | 正确答案

那么问题又来了,锁指的是什么?
观点一:锁是线程进入同步方法时,One类专门去new的一个对象
观点二:锁就是main()中的同一共享资源

2.不同资源的两个同步方法
import java.util.concurrent.TimeUnit;

public class Test04 {
    public static void main(String[] args) {
        //资源1
        One one = new One();
        //资源2
        One tow = new One();

        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    one.first();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"一号").start();

        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    tow.second();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"二号").start();
    }
}

class One{
    public synchronized  void first() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println(Thread.currentThread().getName());
    }
    public synchronized  void second() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
        System.out.println(Thread.currentThread().getName());
    }
}

上述代码可能先输出“一号”,可能先输出“二号”;所以,两个线程获取的是两把不同的锁;推翻了上述的观点一;所以,锁就是资源;就是在main()内new出来的那个资源对象!

3.同一资源的类加载同步方法与普通同步方法
import java.util.concurrent.TimeUnit;

public class Test04 {
    public static void main(String[] args) {
        //资源1
        One one = new One();

        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    one.first();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"一号").start();

        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    one.second();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"二号").start();
    }
}
class One{
    public static synchronized  void first() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println(Thread.currentThread().getName());
    }
    public synchronized  void second() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
        System.out.println(Thread.currentThread().getName());
    }
}

被static修饰的方法,是类加载方法 ;在类一加载时就完成创建了;上述的线程一号和二号,持有的锁是不一样的两把锁!二号线程执行的方法没有被static修饰,所以,线程二号的锁还是资源那把锁one One one = new One();;而一号线程持有的就是 One.class这把锁;任何一个类,在类加载完毕时,都会有一个唯一的class模板

总结
  • 无论是八锁现象还是三锁现象,在意的仅仅是:这个方法是用什么锁来锁定的
  • static修饰的同步方法,持有的锁一定是类.class;且类.class唯一
  • 普通方法不受锁的影响普通方法压根就没得锁,怎么影响?
  • 同步方法的锁一定是资源对象谁调用逻辑方法,谁就是锁

六、多线程下的集合类不安全

List

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;

public class Test01 {
    public static void main(String[] args) {
        /*List*/
        List<String> list = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                /*加入随机数*/
                list.add(UUID.randomUUID().toString().substring(0,3));
                System.out.println(Thread.currentThread().getName()+"===>"+list);
            },"线程"+i).start();
        }
    }
}

在这里插入图片描述

ConcurrentModificationException 线程的并发修改异常

  • ArrayList类为什么不安全?

因为没锁呀,就是这么简单

//ArrayList的add方法没有加锁
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  • 解决方案一:我去给add方法加个锁呗,但是add()是源码,无法修改;JDK官方帮你写了一个加锁的add方法;但是不在ArrayList类中,而在Vector类中
import java.util.*;

public class Test01 {
    public static void main(String[] args) {
        /*List 下实现了 ArrayList类 和 加锁的Vector类  多态 父类引用指向子类对象*/
        List<String> list = new Vector<>();
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                /*加入随机数*/
                list.add(UUID.randomUUID().toString().substring(0,3));
                System.out.println(Thread.currentThread().getName()+"===>"+list);
            },"线程"+i).start();
        }
    }
}

在这里插入图片描述

来看一下Vector类的add()源码 ;是不是加了锁的

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

问大家一个问题:ArrayList类在哪版JDK出来的,Vector类在哪版JDK出来的?
既然,Vector类是为了解决ArrayList类的线程不安全问题,那么肯定是ArrayList类先出来,然后才是Vector类,没错吧

在这里插入图片描述
在这里插入图片描述

上述这两个图,看得出来:ArrayList类是在JDK1.2版本才有的,而Vector类在JDK1.0就有了

  • 解决方案二:使用 Collections.synchronizedList(new ArrayList<>());来创建list集合
import java.util.*;

public class Test01 {
    public static void main(String[] args) {
        /*List*/
        List<String> list = Collections.synchronizedList(new ArrayList<String>());  
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                /*加入随机数*/
                list.add(UUID.randomUUID().toString().substring(0,3));
                System.out.println(Thread.currentThread().getName()+"===>"+list);
            },"线程"+i).start();
        }
    }
}

解决方案三:使用JUC包中的CopyOnWriteArrayList<>();写入时复制!并发线程常用!

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

public class Test01 {
    public static void main(String[] args) {
        /*List*/
        List<String> list = new CopyOnWriteArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                /*加入随机数*/
                list.add(UUID.randomUUID().toString().substring(0,3));
                System.out.println(Thread.currentThread().getName()+"===>"+list);
            },"线程"+i).start();
        }
    }
}

Set

解决方法都类似!只不过Set只有后两种解决方法
1.Set list =Collections.synchronizedSet(new HashSet<>());
使用Collections类转换为同步的方法
2.Set list =new CopyOnWriteArraySet<>();
写入时复制,并发专用

面试题:set集合的底层原理是什么?

HashSet集合的底层就是hashMap;

public HashSet() {
        map = new HashMap<>();
    }

HashSet类的add()是根据什么来实现的?

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

注意:Set、List 用的是add() | Map用的是put()

Map

跟Set一模一样!重点是Map的原理!

七、Callable接口(解析)

Java-API文档解说Callable接口

在这里插入图片描述

  • 实现一个Callable的接口,就会返回T类型的数据
  • 重写call方法,且不能传参
  • 最重要的一点:可能抛出异常!!!

你有见过main线程抛出异常吗?Runnable接口能抛出异常吗?不能!为啥?

有异常抛出,就一定要处理异常!main线程是主线程,它本来就是最底层了,它能抛给谁呀?对吧。那么,Callable接口能抛出异常,就说明它下面还有个东西去给它兜底!这里先做个铺垫

Callable接口的使用

  • 只要是线程,都必须得 new Thread(XXX); 看一下Thread的构造器
    在这里插入图片描述

哎!没有 **Thread(Callable target)**这个构造器呀;那我怎么搞一个线程出来?

  1. 那就先将就一下吧,先传一个实现了Runnable接口的对象来吧;Runnable接口的实现类有很多!
    在这里插入图片描述

  2. 我就直白说了!FutureTask类作为Runnable接口的实现类之一;它的构造方法有:
    在这里插入图片描述

所以,回到之前那个问题:凭什么Callable接口能抛出异常?
因为,Callable接口经过FutureTask类的转换后,还是变成了Runnable接口的对象!

代码案例

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

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

        MyCallable myCallable = new MyCallable();
        //future 未来
        FutureTask<String> stringFutureTask = new FutureTask<>(myCallable);
        new Thread(stringFutureTask,"线程一号").start();
        //获取返回值
        try {
            String s = stringFutureTask.get();
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "======> ");
        return "返回的字符串类型";
    }
}

在这里插入图片描述

八、多线程常用的辅助类

类似于减法 CountDownLatch

import java.util.concurrent.CountDownLatch;

public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        /*倒计时 20次*/
        CountDownLatch countDownLatch = new CountDownLatch(20);

        Rice rice = new Rice();

        /*假设有15个线程顾客*/
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                rice.eat();
                //计数器 减1
                countDownLatch.countDown();
                },"第"+i+"位顾客").start();

        }
        //等待计数器归零,才执行后续代码逻辑
        countDownLatch.await();

        System.out.println("我是"+Thread.currentThread().getName()+"==>我要打烊了");
    }
}

class Rice{
    void eat(){
        System.out.println(Thread.currentThread().getName() + "==>吃好了");
    }
}

在这里插入图片描述

类似于加法 CyclicBarrier

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Test03 {
    public static void main(String[] args) throws InterruptedException {

        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("当计数达到7时,我才执行");
        });

        for (int i = 0; i <= 7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName() + "我给计数器cyclicBarrier加了1");
                    //每执行这串代码一次,计数加 1
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },i+"线程").start();
        }
    }
}

在这里插入图片描述在这里插入图片描述

限定线程个数 Semaphore

在这里插入图片描述

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class Test04 {
    public static void main(String[] args) {
        /*限流 限定线程的数量
        * 可以这么理解:一桌八个位置;现在有20个人;怎么才能保证剩余的12人不去抢??
        * */
        Semaphore semaphore = new Semaphore(8);

        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                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();
                }
            },"我是第"+i+"个人").start();
        }
    }
}

在这里插入图片描述

九、 读写锁 ReadWriteLock接口

在这里插入图片描述

写操作: 假如你和你女朋友只有一个手机,你正在玩王者,玩到一半;你女朋友就把手机抢了;你是不是很有难受?所以,要设置一把锁(独占锁);把你关在一个没人打扰的房间内,保证你打王者的原子性
读操作:就如你还是在打游戏;你的朋友在旁边看着你打;无所谓呀,反正他们又不抢我手机;想看就看呗,不看就走呗,不用排队,不用上锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test01 {
    /*
    * 描述这样一个场景:
    *   1.你和你女朋友都想玩游戏,手机只有一个,玩游戏的时候不准相互打扰
    *   2.你们周围有很多大妈,只能看你们玩游戏,看了就走;不用排队
    * */
    public static void main(String[] args) {

        Game game = new Game();

        new Thread(()->{
            game.play();
        },"男朋友").start();

        new Thread(()->{
            game.play();
        },"女朋友").start();

        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                game.look();
            },i+"号围观大妈").start();
        }
    }
}

class Game{

    /*lock锁*/
    Lock lock = new ReentrantLock();
    /*读写锁*/
    ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void play(){
        /*定义写锁 上锁*/
        reentrantReadWriteLock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"开始玩游戏了");
            TimeUnit.SECONDS.sleep(2);
            System.out.println(Thread.currentThread().getName()+"赢了");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            /*释放写锁*/
            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    public void look(){

        /*定义读锁 上锁*/
        reentrantReadWriteLock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"开始看小伙打游戏");
            TimeUnit.SECONDS.sleep(2);
            System.out.println(Thread.currentThread().getName()+"操作真烂,走了");

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            /*释放写锁*/
            reentrantReadWriteLock.readLock().unlock();
        }
    }
}

在这里插入图片描述

读写锁给我的感觉就像是:写操作加了锁对象;而读操作没有加锁对象。同一个锁有两种状态;即一锁二用

十、阻塞队列BlockingQueue

什么是队列?

队列是一种数据结构,FIFO (先进先出);列如,排队,管道,火车过隧道等

在这里插入图片描述

可以看出:BlockingQueue接口的父接口Queue是与Set、List接口同级的!

什么时候会造成阻塞??

把写入比作吃饭,把取出比作上厕所
1.当你吃饱了,没有一点空间了;这个时候你是不是得等待一下?先拉出来再吃嘛;这种就是阻塞等待问题
2.当你很饿,肚子内没有存货了;你怎么拉?拉空气吗?是不是先得等待吃了,再拉?这也是阻塞等待问题
BlockingQueue就是来解决这个问题的

ArrayBlockingqueue的四种API

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Test02 {


    public static void main(String[] args) throws InterruptedException {

//          oneWay();
//          towWay();
//          threeWay();
//          fore();
    }

    /*1.第一种API 会抛出异常 即:报异常*/
    public static void oneWay(){
        /*参数代表:这个队列能容纳多少个单位;即能放入多少次*/
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(5);

        /*放入数据 add() 返回的是一个boolean*/
        arrayBlockingQueue.add("one");
        arrayBlockingQueue.add("tow");
        arrayBlockingQueue.add("three");
        arrayBlockingQueue.add("fore");
        arrayBlockingQueue.add("five");
        /*此时,在放入一次的话,就会使这个队列阻塞等待 报 java.lang.IllegalStateException异常*/
//        arrayBlockingQueue.add("six");

        /*取出数据 按照先进先出的顺序取值*/
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        /*此时,无值可取,队列阻塞等待 会报 java.util.NoSuchElementException异常*/
//        arrayBlockingQueue.remove();

    }
    /*2.第二种API 当队列阻塞时,不会报异常,有返回值 (不会耽搁程序的执行)*/
    public static void towWay(){
        ArrayBlockingQueue<Integer> arrayBlockingQueue = new ArrayBlockingQueue<>(4);
        
        /*放入值 方法为 offer()  若能放入队列,则返回true ; 若不能,则返回false*/
        System.out.println(arrayBlockingQueue.offer(1));
        System.out.println(arrayBlockingQueue.offer(2));
        System.out.println(arrayBlockingQueue.offer(3));
        System.out.println(arrayBlockingQueue.offer(4));
        /*此时,返回false*/
        System.out.println(arrayBlockingQueue.offer(5));

        /*取出值 poll() 若取出成功,则返回取出值 ; 若取出失败,则返回 null*/
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        /*此时,返回null*/
        System.out.println(arrayBlockingQueue.poll());
    }

    /*第三种API 超时等待: 在指定的时间内阻塞等待,过了这个时间就退出了*/
    public static void  threeWay() throws InterruptedException {
        ArrayBlockingQueue<Character> arrayBlockingQueue = new ArrayBlockingQueue<>(4);
        /*放入值 方法为 offer(值,时间,时间单位)  若能放入队列,则返回true ; 若不能,则等待指定时间,若还是不能,就返回false*/
        System.out.println(arrayBlockingQueue.offer('A',2,TimeUnit.SECONDS));
        System.out.println(arrayBlockingQueue.offer('B',2,TimeUnit.SECONDS));
        System.out.println(arrayBlockingQueue.offer('C',2,TimeUnit.SECONDS));
        System.out.println(arrayBlockingQueue.offer('D',2,TimeUnit.SECONDS));
        /*此时,队列满了;只能阻塞等待;等个2秒,发现还是放不进去,就返回false*/
        System.out.println(arrayBlockingQueue.offer('E',2,TimeUnit.SECONDS));

        /*取出值 一样的道理*/
        System.out.println(arrayBlockingQueue.poll(2,TimeUnit.SECONDS));
        System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
        System.out.println(arrayBlockingQueue.poll(1,TimeUnit.SECONDS));
        System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
        /*此时,队列无值,等待了4秒后,发现还是没有值,就返回 null*/
        System.out.println(arrayBlockingQueue.poll(4,TimeUnit.SECONDS));
    }
    
    /*4.第四种API 一直等待 就像个杠精*/
    public static void fore() throws InterruptedException {
        ArrayBlockingQueue<Double> arrayBlockingQueue = new ArrayBlockingQueue<Double>(3);

        /*放入 这里是没有返回值的*/
        arrayBlockingQueue.put(2.33);
        arrayBlockingQueue.put(1.26);
        arrayBlockingQueue.put(9.36);
        /*此时,放不进去了;会一直阻塞,直到能放进去为zhi*/
//        arrayBlockingQueue.put(2.33);
        
        /*取出 会返回取出值*/
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
    }
}

总结

方法需要抛出异常不需要抛出异常,但有返回值超时等待,也有返回值一直等待
放入add()offer()offer(value,time,TimeUtil.SECOND)put()
取出remove()poll()poll(value,time,TimeUtil.SECOND)take()

java-PAI文档还有很多的常用方法,自己去看哟

同步队列SynchronousQueue

在这里插入图片描述

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class Test03 {

    public static void main(String[] args) {

        /*没有容量 同步队列*/
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue();

        new Thread(()->{
            try {
                synchronousQueue.put("1");
                synchronousQueue.put("2");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"put线程").start();
        new Thread(()->{
            try {
                System.out.println(synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);

                System.out.println(synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);

                System.out.println(synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);


            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"take线程").start();
    }
}

十一、线程池的使用

三种创建线程池的方法

1.创建单一线程容量的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test04 {
    public static void main(String[] args) {
        /*
        * Executors 线程池工具类 就相当于创建了一个池子,线程就是鱼儿
        * */
        /*newSingleThreadException() 表示在线程池中只能有一个线程*/
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        try {
            for (int i = 0; i < 10; i++) {
                int temp = i;
                /*
                * 学会了线程池,就没必要用 new Thread(()->{}) 来创建线程了
                *   1.线程池创建的线程,没有 start()去启动,是自动启动的
                *   2.线程池运行完毕后,一定要记得关闭 线程池对象.shutdown()
                *   3.借助temp中间值,来传递i值
                * */
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + ": " + temp);
                });
            }
        }catch (Exception e) {
                e.printStackTrace();
            }
        finally {
            executorService.shutdown();
        }
    }
}

在这里插入图片描述

2.创建指定线程容量的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test04 {
    public static void main(String[] args) {
        /*
        * Executors 线程池工具类 就相当于创建了一个池子,线程就是鱼儿
        * */
        /*newFixedThreadPool(X) 表示在线程池中最多有X个线程*/
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        try {
            for (int i = 0; i < 10; i++) {
                int temp = i;
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + ": " + temp);
                });
            }
        }catch (Exception e) {
                e.printStackTrace();
            }
        finally {
            executorService.shutdown();
        }
    }
}
3.缓冲线程池 具体有多少个线程,由CPU的性能来决定
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test04 {
    public static void main(String[] args) {
        /*
        * Executors 线程池工具类 就相当于创建了一个池子,线程就是鱼儿
        * */
        /*newCachedThreadPool() 我也不知道有多少个线程 一般在30几个线程左右**/
        ExecutorService executorService = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 10; i++) {
                int temp = i;
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + ": " + temp);
                });
            }
        }catch (Exception e) {
                e.printStackTrace();
            }
        finally {
            executorService.shutdown();
        }
    }
}

深度分析创建线程池三种方法的源码

Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
Executors.newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

可以看出,三种方法都实现了ThreadPoolExecutor这个接口;并且传入了七个参数;作为一个优秀的程序员,创建线程池就不用Executors.三种方法;而是模仿源码写一个自定义的,高精细的,契合自身代码的线程池;看一下 ThreadPoolExecutor接口的源码

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

十二、创建一个线程池的七大参数

参数描述
int corePoolSize核心线程池的大小
int maximumPoolSize最大核心线程池大小
long keepAliveTime超时释放,时间一到,没有用我,我就释放了
TimeUnit unit超时的时间单位
BlockingQueue workQueue阻塞队列
ThreadFactory threadfactory线程工厂 一般不动
RejectedExecutionHandler handle拒绝策略

现实场景模拟线程池的七大参数

有这么一家银行,它有五个窗口;有能容纳8个人的等待区;只要等待区没有坐满8个人,它就不会把窗口全开,仅仅开两个窗口就行了。有一天,上午来了好多人办理业务;等待区都坐满了,没办法,只能把所有窗口打开;但是,还是有人一直进来;这个时候的银行保安就说:“不准进了,已经没有空间了”;一直忙到下午;当等待区有一丢丢空位子时,那三个窗口就关了;剩下两个窗口在继续忙;一直到没有人为止

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test05 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                /*当队列正常运行时,开启的线程数  平时办理业务的 两个窗口*/
                2,
                /*当阻塞队列的容量满了后,即:阻塞队列开始阻塞 开启的线程数  忙时,所有窗口都要用*/
                5,
                /*当队列不阻塞时,开始计数 超过三秒 就把那些不常用的线程关了*/
                3,
                /*超时单位*/
                TimeUnit.SECONDS,
                /*这个就是队列 指定了容量为4 即: 等待去只有四个位置*/
                new ArrayBlockingQueue<>(4),
                /*线程的工厂类*/
                Executors.defaultThreadFactory(),
                /*下列是四种拒绝策略 就是银行保安说的话*/
                /*1.银行已经满了,如果还有人来;我就抛出异常 报异常*/
//              new ThreadPoolExecutor.AbortPolicy()
                /*2.银行已经满了,如果还有人来;我就让他从哪里来回那里去;*/
//              new ThreadPoolExecutor.CallerRunsPolicy()
                /*3.银行已经满了,如果还有人来;我就去看看最早办理业务的那个人,看他办完没;若办完了,我就让他进去*/
                new ThreadPoolExecutor.DiscardOldestPolicy()
                /*4.不抛出异常,就当这个人没来过*/
//              new ThreadPoolExecutor.DiscardPolicy()
        );
        /*20个顾客*/
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(()->{
                System.out.println(Thread.currentThread().getName()+"===>办理业务");
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

**注意:**关闭线程池!!!

最大线程数的设置 CPU密集型 IO密集型

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test05 {
    public static void main(String[] args) {
        /*获取CPU的核数*/
        System.out.println(Runtime.getRuntime().availableProcessors());

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                /*当队列正常运行时,开启的线程数  平时办理业务的 两个窗口*/
                2,
                /*当阻塞队列的容量满了后,即:阻塞队列开始阻塞 开启的线程数  忙时,所有窗口都要用
                *   CPU 密集型  看自己的电脑,有几核处理器,就写几
                *   IO 密集型 先判断你的程序中有多少个消耗IO流的线程;然后设置为2倍大小
                * */
                Runtime.getRuntime().availableProcessors(),
                /*当队列不阻塞时,开始计数 超过三秒 就把那些不常用的线程关了*/
                3,
                /*超时单位*/
                TimeUnit.SECONDS,
                /*这个就是队列 指定了容量为4 即: 等待去只有四个位置*/
                new ArrayBlockingQueue<>(4),
                /*线程的工厂类*/
                Executors.defaultThreadFactory(),
                /*下列是四种拒绝策略 就是银行保安说的话*/
                /*1.银行已经满了,如果还有人来;我就抛出异常 报异常*/
//              new ThreadPoolExecutor.AbortPolicy()
                /*2.银行已经满了,如果还有人来;我就让他从哪里来回那里去;*/
//              new ThreadPoolExecutor.CallerRunsPolicy()
                /*3.银行已经满了,如果还有人来;我就去看看最早办理业务的那个人,看他办完没;若办完了,我就让他进去*/
                new ThreadPoolExecutor.DiscardOldestPolicy()
                /*4.不抛出异常,就当这个人没来过*/
//              new ThreadPoolExecutor.DiscardPolicy()
        );
       try{
           /*20个顾客*/
           for (int i = 0; i < 10; i++) {
               threadPoolExecutor.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"===>办理业务");
                   try {
                       TimeUnit.SECONDS.sleep(4);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               });
           }
       }catch (Exception e){
           e.printStackTrace();
       }finally{
           threadPoolExecutor.shutdown();
       }
    }
}

十三、四大函数式接口

一个接口内,只有一个方法

1. Function 接口

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);
}
import java.util.function.Function;

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

        /*传入参数类型和返回值类型可以自己定义,Function<String,object> */
        Function<String, Object> Function01 = new Function<String, Object>() {
            @Override
            public Object apply(String s) {
                return s+"你好";
            }
        };
        /*lambda 表达式*/
        Function<String, Object> Function02 =(str)->{ return str+"早上好"; };

        System.out.println(Function01.apply("陌生人"));
        System.out.println(Function02.apply("你这个B"));
    }
}

2.断定型接口Predicate

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);
}
import java.util.function.Predicate;

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

        /*Predicate<输入值的类型即泛型> 输入一个指定类型的值,返回一个Boolean值 */
        Predicate<Integer> integerPredicate01 = new Predicate<Integer>() {
            @Override
            public boolean test(Integer message) {
               return message.equals(0);
            }
        };
        /*lambda*/
        Predicate<Integer> integerPredicate02 =(mes)->{return mes.equals(0);};

        System.out.println(integerPredicate01.test(12));
        System.out.println(integerPredicate02.test(0));
    }
}

3.消费型接口Consumer

只有传入参数,没有返回值

public interface Consumer<T> {

    void accept(T t);
}

4.供给型接口Supplier

没有传入值,只有返回值!返回值可以自己定义类型

@FunctionalInterface
public interface Supplier<T> {

    T get();
}

记住:所有的函数式接口都可以用Lambda来简写

十四、Stream并行流

import java.util.stream.LongStream;

public class Time {

    public static void main(String[] args) {
        Time time = new Time();
        System.out.println(time.forTest());
        System.out.println("--------------");
        System.out.println(time.streamTest());
    }

    public long forTest(){
        long start = System.currentTimeMillis();
        long sum = 0;

        for (int i = 0; i <10_0000_0000_0l; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("for=>>"+sum);
        return end-start;
    }

    public long streamTest(){
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0l, 10_0000_0000_0l).parallel().reduce(0,Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("Steam=>>"+sum);
        return end-start;
    }
}

当有多个任务时,使用Stream流可以提高效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值