JUC –java高并发总结(上)

导读:本篇文章讲解 JUC –java高并发总结(上),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.什么是 JUC

JUC就是 java.util 下的工具包、包、分类等。

在这里插入图片描述

普通的线程代码:

  • Thread
  • Runnable 没有返回值、效率相比入 Callable 相对较低!
  • Callable 有返回值!

2.线程和进程

  • 进程:一个程序,QQ.exe Music.exe 程序的集合;
  • 一个进程往往可以包含多个线程,至少包含一个!
  • Java默认有2个线程? mian、GC
  • 线程:开了一个进程 Typora,写字,自动保存(线程负责的)
  • 对于Java而言提供了:Thread、Runnable、Callable操作线程。

Java 真的可以开启线程吗? 答案是:开不了的!底层操作是c++,java无法直接操作硬件

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. 
     */
    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 */
        }
    }
}
// 本地方法,底层操作的是C++ ,Java 无法直接操作硬件
private native void start0();

并发、并行

并发(多线程操作同一个资源)

  • 一核CPU,模拟出来多条线程,快速交替。

并行(多个人一起行走)

  • 多核CPU ,多个线程可以同时执行; eg: 线程池!

并发编程的本质:充分利用CPU的资源

线程有几个状态(6个)

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     * 线程新生状态
     */
    NEW,
    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     * 线程运行中
     */
    RUNNABLE,
    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     * 线程阻塞状态
     */
    BLOCKED,
    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     * 线程等待状态,死等
     */
    WAITING,
    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     * 线程超时等待状态,超过一定时间就不再等
     */
    TIMED_WAITING,
    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     * 线程终止状态,代表线程执行完毕
     */
    TERMINATED;
}

wait/sleep 区别

1、二者来自不同的类

  • wait => Object
  • sleep => Thread
    2、关于锁的释放
  • wait 会释放锁
  • sleep 睡觉了,抱着锁睡觉,不会释放!
    3、使用的范围是不同的
  • wait 必须在同步代码块中使用
  • sleep 可以再任何地方睡眠

3、Synchronized锁

3.1传统 Synchronized锁

来看一个多线程卖票例子

package com.haust.juc01;
/*
 * @Auther: csp1999
 * @Date: 2020/07/21/13:59
 * @Description: 卖票例子
 */
public class SaleTicketTDemo01 {
    /*
     * 真正的多线程开发,公司中的开发,降低耦合性
     * 线程就是一个单独的资源类,没有任何附属的操作!
     * 1、 属性、方法
     */
    public static void main(String[] args) {
        //并发:多个线程同时操作一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();
        // @FunctionalInterface 函数式接口,jdk1.8 lambada表达式
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "C").start();
    }
}
//资源类 OOP
class Ticket {
    //属性、方法
    private int number = 50;
    // 卖票的方式
    // synchronized 本质: 队列,锁
    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了" +
                    (50-(--number)) + "张票,剩余:" + number + "张票");
        }
    }
}

4、Lock锁(重点)

Lock 接口
在这里插入图片描述

在这里插入图片描述

  • 公平锁:十分公平,线程执行顺序按照先来后到顺序
  • 非公平锁:十分不公平:可以插队 (默认锁)

将上面的卖票例子用lock锁 替换synchronized:

package com.haust.juc01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
 * @Auther: csp1999
 * @Date: 2020/07/21/13:59
 * @Description: 卖票例子2
 */
public class SaleTicketTDemo02 {
    public static void main(String[] args) {
        //并发:多个线程同时操作一个资源类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        // @FunctionalInterface 函数式接口,jdk1.8 lambada表达式
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 50; i++) {
                ticket.sale();
            }
        }, "C").start();
    }
}
//Lock 3步骤
// 1. new ReentrantLock();
// 2. lock.lock()  加锁
// 3. lock.unlock() 解锁
class Ticket2 {
    //属性、方法
    private int number = 50;
    Lock lock = new ReentrantLock();
    // 卖票方式
    public void sale() {
        lock.lock();// 加锁
        try {
            // 业务代码
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了" +
                        (50 - (--number)) + "张票,剩余:" + number + "张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();// 解锁
        }
    }
}

Synchronized 和 Lock 区别:

  • 1、Synchronized 内置的Java关键字, Lock 是一个Java类
  • 2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  • 3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
  • 4、Synchronized 线程 1(获得锁,如果线程1阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去
  • 5、Synchronized 可重入锁,不可以中断的,非公平Lock可重入锁,可以判断锁,非公平(可以自己设置)
  • 6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

5、生产者和消费者问题

面试常考的问题:单例模式、排序算法、生产者和消费者、死锁

生产者和消费者问题 Synchronized 版

package com.haust.pc;
/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒,通知唤醒
 * 线程交替执行  A   B 操作同一个变量   num = 0
 * A num+1
 * B num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}
// 判断等待,业务,通知
class Data {
     // 数字 资源类
    private int number = 0;
    //+1
    public synchronized void increment() throws InterruptedException {
        /*
        假设 number此时等于1,即已经被生产了产品
        如果这里用的是if判断,如果此时A,C两个生产者线程争夺increment()方法执行权
        假设A拿到执行权,经过判断number!=0成立,则A.wait()开始等待(wait()会释放锁),然后C试图去执行
        生产方法,但依然判断number!=0成立,则B.wait()开始等待(wait()会释放锁)
        碰巧这时候消费者线程线程B/D去消费了一个产品,使number=0然后,B/D消费完后调用this.notifyAll();
        这时候2个等待中的生产者线程继续生产产品,而此时number++ 执行了2次
        同理,重复上述过程,生产者线程继续wait()等待,消费者调用this.notifyAll();
        然后生产者继续超前生产,最终导致‘产能过剩’,即number大于1
        if(number != 0){
            // 等待
            this.wait();
        }*/
        while (number != 0) {
     // 注意这里不可以用if 否则会出现虚假唤醒问题,解决方法将if换成while
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我+1完毕了
        this.notifyAll();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        // 通知其他线程,我-1完毕了
        this.notifyAll();
    }
}

问题存在:A B C D 4 个线程! 虚假唤醒

首先到CHM 官方文档 java.lang包下 找到Object ,然后找到wait()方法:
在这里插入图片描述
因此上述代码中必须使用while判断,而不能使用if

JUC版的生产者和消费者问题

在这里插入图片描述
在这里插入图片描述
点入Condition 查看
在这里插入图片描述
在这里插入图片描述

package com.haust.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒,通知唤醒
 * 线程交替执行  A   B 操作同一个变量   num = 0
 * A num+1
 * B num-1
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}
// 判断等待,业务,通知
class Data2 {
     // 数字 资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //condition.await(); // 等待 
    //condition.signalAll(); // 唤醒全部
    //+1
    public  void increment() throws InterruptedException {
        lock.lock();
        try {
            // 业务代码
            while (number != 0) {
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,我+1完毕了
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //-1
    public  void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                // 等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程,我-1完毕了
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,是有其对旧技术的优势和补充!

Condition 精准的通知和唤醒线程

在这里插入图片描述
问题:ABCD线程 抢占执行的顺序是随机的,如果想让ABCD线程有序执行,该如何改进代码?

package com.haust.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
 * A 执行完调用B,B执行完调用C,C执行完调用A
 */
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        }, "C").start();
    }
}
class Data3 {
     // 资源类 Lock
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; 
    // number=1 A执行  number=2 B执行 number=3 C执行
    public void printA() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number != 1) {
                // A等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAAAAAA");
            // 唤醒,唤醒指定的人,B
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number != 2) {
                // B等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBB");
            // 唤醒,唤醒指定的人,c
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            // 业务,判断-> 执行-> 通知
            while (number != 3) {
                // C等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>CCCCC ");
            // 唤醒,唤醒指定的人,A
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

6. 8锁现象

synchronized 锁的对象是方法的调用者

package com.haust.lock8;
import java.util.concurrent.TimeUnit;
/**
 * 8锁,就是关于锁的8个问题
 * 1、标准情况下,两个线程先打印 发短信还是 先打印 打电话? 1/发短信  2/打电话
 * 1、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信  2/打电话
 */.
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        // 锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone{
    // synchronized 锁的对象是方法的调用者!、
    // 两个方法用的是同一个对象调用(同一个锁),谁先拿到锁谁执行!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);// 抱着锁睡眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
// 先执行 发短信,后执行打电话

普通方法没有锁!不是同步方法,就不受锁的影响,正常执行

代码举例2

package com.haust.lock8;
import java.util.concurrent.TimeUnit;
/**
 * 3、 增加了一个普通方法后!先执行发短信还是Hello?// 普通方法
 * 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
 */
public class Test2  {
    public static void main(String[] args) {
        // 两个对象,两个调用者,两把锁!
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
        new Thread(()->{
            phone2.hello();
        },"C").start();
    }
}
class Phone2{
    // synchronized 锁的对象是方法的调用者!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    // 这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}
// 先执行打电话,接着执行hello,最后执行发短信

**不同实例对象的Class类模板只有一个,static静态的同步方法,锁的是Class **
代码举例3:

package com.haust.lock8;
import java.util.concurrent.TimeUnit;
/**
 * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
 * 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
 */
public class Test3  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
// Phone3唯一的一个 Class 对象
class Phone3{
    // synchronized 锁的对象是方法的调用者!
    // static 静态方法
    // 类一加载就有了!锁的是Class
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    } 
    public static synchronized void call(){
        System.out.println("打电话");
    }
}
// 先执行发短信,后执行打电话

代码举例4:

package com.haust.lock8;
import java.util.concurrent.TimeUnit;
/**
 * 7、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
 * 8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
 */
public class Test4  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
// Phone3唯一的一个 Class 对象
class Phone4{
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 普通的同步方法  锁的调用者(对象),二者锁的对象不同,所以不需要等待
    public synchronized void call(){
        System.out.println("打电话");
    }
}
// 7/8 两种情况下,都是先执行打电话,后执行发短信,因为二者锁的对象不同,
// 静态同步方法锁的是Class类模板,普通同步方法锁的是实例化的对象,
// 所以不用等待前者解锁后 后者才能执行,而是两者并行执行,因为发短信休眠4s
// 所以打电话先执行。

小结:

  • new this 具体的一个手机
  • static Class 唯一的一个模板

7、集合类不安全

List 不安全

List、ArrayList 等在并发多线程条件下,不能实现数据共享,多个线程同时调用一个list对象时候就会出现并发修改异常ConcurrentModificationException 。

代码举例:

package com.haust.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
// java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
    public static void main(String[] args) {
        // 并发下 ArrayList 不安全的吗,Synchronized;
        /*
         * 解决方案;
         * 方案1、List<String> list = new Vector<>();
         * 方案2、List<String> list =
         * Collections.synchronizedList(new ArrayList<>());
         * 方案3、List<String> list = new CopyOnWriteArrayList<>();
         */
       /* CopyOnWrite 写入时复制  COW  计算机程序设计领域的一种优化策略;
        * 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
        * 在写入的时候避免覆盖,造成数据问题!
        * 读写分离
        * CopyOnWriteArrayList  比 Vector Nb 在哪里?
        */    
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

Set 不安全

Set、Hash 等在并发多线程条件下,不能实现数据共享,多个线程同时调用一个set对象时候就会出现并发修改异常ConcurrentModificationException 。

代码举例:

package com.haust.unsafe;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
 * 同理可证 : ConcurrentModificationException 并发修改异常
 * 1、Set<String> set = 
 *                     Collections.synchronizedSet(new HashSet<>());
 * 2、
 */
public class SetTest {
    public static void main(String[] args) {
        //Set<String> set = new HashSet<>();//不安全
        // Set<String> set = Collections.synchronizedSet(new HashSet<>());//安全
        Set<String> set = new CopyOnWriteArraySet<>();//安全
        for (int i = 1; i <=30 ; i++) {
           new Thread(()->{
               set.add(UUID.randomUUID().toString().substring(0,5));
               System.out.println(set);
           },String.valueOf(i)).start();
        }
    }
}

Map 不安全

在这里插入图片描述
代码举例:



package com.haust.unsafe;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
// ConcurrentModificationException
public class  MapTest {
    public static void main(String[] args) {
        // map 是这样用的吗? 不是,工作中不用 HashMap
        // 默认等价于什么?  new HashMap<>(16,0.75);
        // Map<String, String> map = new HashMap<>();
        // 扩展:研究ConcurrentHashMap的原理
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 1; i <=30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),
                       UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

8、Callable (简单)

在这里插入图片描述

Callable 和 Runable 对比:

举例:比如Callable 是你自己,你想通过你的女朋友 Runable 认识她的闺蜜 Thread
在这里插入图片描述
在这里插入图片描述

  • Callable 是 java.util 包下 concurrent 下的接口,有返回值,可以抛出被检查的异常
  • Runable 是 java.lang 包下的接口,没有返回值,不可以抛出被检查的异常
  • 二者调用的方法不同,run()/ call()

同样的 Lock 和 Synchronized 二者的区别,前者是java.util 下的接口 后者是 java.lang 下的关键字。

package com.haust.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * 1、探究原理
 * 2、觉自己会用
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // new Thread(new Runnable()).start();// 启动Runnable
        // new Thread(new FutureTask<V>()).start();
        // new Thread(new FutureTask<V>( Callable )).start();
        new Thread().start(); // 怎么启动Callable?
        // new 一个MyThread实例
        MyThread thread = new MyThread();
        // MyThread实例放入FutureTask
        FutureTask futureTask = new FutureTask(thread); // 适配类
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start(); // call()方法结果会被缓存,提高效率,因此只打印1个call
        // 这个get 方法可能会产生阻塞!把他放到最后
        Integer o = (Integer) futureTask.get(); 
        // 或者使用异步通信来处理!
        System.out.println(o);// 1024
    }
}
class MyThread implements Callable<Integer> {
    @Override
    public Integer call() {
        System.out.println("call()"); // A,B两个线程会打印几个call?(1个)
        // 耗时的操作
        return 1024;
    }
}
//class MyThread implements Runnable {
//
//    @Override
//    public void run() {
//        System.out.println("run()"); // 会打印几个run
//    }
//}

细节:

1、有缓存

2、结果可能需要等待,会阻塞!

9、常用的辅助类(必会)

9.1、CountDownLatch

在这里插入图片描述
减法计数器: 实现调用几次线程后 再触发某一个任务

代码举例:

package com.haust.add;    
import java.util.concurrent.CountDownLatch;
// 计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数是6,必须要执行任务的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; 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("Close Door");
    }
}

原理:

countDownLatch.countDown(); // 数量-1

countDownLatch.await(); // 等待计数器归零,然后再向下执行

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

9.2、CyclicBarrier

在这里插入图片描述
加法计数器:集齐7颗龙珠召唤神龙

代码举例:

package com.haust.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class  CyclicBarrierDemo {
    public static void main(String[] args) {
        /*
         * 集齐7颗龙珠召唤神龙
         */
        // 召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });
        for (int i = 1; i <=7 ; i++) {
            final int temp = i;
            // lambda能操作到 i 吗
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()
                                                 +"收集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

9.3、Semaphore

Semaphore:信号量
在这里插入图片描述
限流/抢车位!6车—3个停车位置
代码举例:

package com.haust.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量:停车位! 限流!、
        // 如果已有3个线程执行(3个车位已满),则其他线程需要等待‘车位’释放后,才能执行!
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                // 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(); // release() 释放 
                }
            },String.valueOf(i)).start();
        }
    }
}

只有三个车位,只有当某辆车离开车位,车位空出来后,下一辆车才能在此停放。

输出结果如图:
在这里插入图片描述
原理:

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

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

10、读写锁 ReadWriteLock

ReadWriteLock
在这里插入图片描述
在这里插入图片描述
代码举例:

package com.haust.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock
 * 读-读  可以共存!
 * 读-写  不能共存!
 * 写-写  不能共存!
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        //MyCache myCache = new MyCache();
        MyCacheLock myCacheLock = new MyCacheLock();
        // 写入
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCacheLock.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCacheLock.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}
/**
 * 自定义缓存
 * 加锁的
 */
class MyCacheLock{
    private volatile Map<String,Object> map = new HashMap<>();
    // 读写锁: 更加细粒度的控制
    private ReadWriteLock readWriteLock = new             
                                    ReentrantReadWriteLock();
    // private Lock lock = new ReentrantLock();
    // 存,写入的时候,只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()
                                                       +"写入"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()
                                                       +"写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    // 取,读,所有人都可以读!
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()
                                                       +"读取"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()
                                                       +"读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
/**
 * 自定义缓存
 * 不加锁的
 */
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()
                                                       +"写入OK");
    }
    // 取,读
    public void get(String key){
        System.out.println(Thread.currentThread().getName()
                                                       +"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()
                                                       +"读取OK");
    }
}

执行效果如图:
在这里插入图片描述
在这里插入图片描述

11、阻塞队列

在这里插入图片描述
阻塞队列:
在这里插入图片描述
在这里插入图片描述

BlockingQueue
在这里插入图片描述
什么情况下我们会使用 阻塞队列?:多线程并发处理,线程池用的较多 !

学会使用队列

添加、移除
四组API

方式 抛出异常 有返回值,不抛出异常 阻塞 等待 超时等待
添加 add offer() put() offer(,)
移除 remove(现在不会抛出异常有返回值) poll() take() poll(,)
检测队首元素 element peek()

代码示例:

package com.kuang.bq;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) throws InterruptedException {
        test4();
    }
    /**
     * 1. 无返回值,抛出异常的方式
     */
    public static void test1(){
        // 队列的大小
        ArrayBlockingQueue blockingQueue = 
                                    new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));// true
        System.out.println(blockingQueue.add("b"));// true
        System.out.println(blockingQueue.add("c"));// true
        // System.out.println(blockingQueue.add("d"));
        // IllegalStateException: Queue full 抛出异常---队列已满!
        System.out.println("===========================");
        System.out.println(blockingQueue.element());//
        // 查看队首元素是谁
        System.out.println(blockingQueue.remove());//
        System.out.println(blockingQueue.remove());//
        System.out.println(blockingQueue.remove());//
        // System.out.println(blockingQueue.remove());
        // java.util.NoSuchElementException 抛出异常---队列已为空!
    }
    /**
     * 2. 有返回值,不抛出异常的方式
     */
    public static void test2(){
        // 队列的大小
        ArrayBlockingQueue blockingQueue = 
                                    new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.peek());
        // System.out.println(blockingQueue.offer("d")); 
        // false 不抛出异常!
        System.out.println("===========================");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll()); 
        // null  不抛出异常!
    }
    /**
     * 3. 等待,阻塞(一直阻塞)
     */
    public static void test3() throws InterruptedException {
        // 队列的大小
        ArrayBlockingQueue blockingQueue = 
                                    new ArrayBlockingQueue<>(3);
        // 一直阻塞
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        // blockingQueue.put("d"); // 队列没有位置了,一直阻塞等待
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take()); 
        // 没有这个元素,一直阻塞等待
    }
    /**
     * 4. 等待,阻塞(等待超时)
     */
    public static void test4() throws InterruptedException {
        // 队列的大小
        ArrayBlockingQueue blockingQueue = 
                                    new ArrayBlockingQueue<>(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        // blockingQueue.offer("d",2,TimeUnit.SECONDS); 
        // 等待超过2秒就退出
        System.out.println("===============");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        blockingQueue.poll(2,TimeUnit.SECONDS); // 等待超过2秒就退出
    }
}

SynchronousQueue 同步队列

put、take
代码举例:

package com.haust.bq;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
 * 同步队列:
 * 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
 * put了一个元素,必须从里面先take取出来,否则不能在put进去值!
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = 
                                new SynchronousQueue<>(); // 同步队列
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()
                                                           +" put 1");
                // put进入一个元素
                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 {
                // 睡眠3s取出一个元素
                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();
    }
}

结果:
在这里插入图片描述

12、线程池(重点)

池化技术

程序的运行,本质:占用系统的资源! (优化资源的使用 => 池化技术)

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

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

线程池的好处:

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

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

线程池:3大方法

在这里插入图片描述

package com.haust.pool;
import java.util.concurrent.ExecutorService;
import java.util.List;
import java.util.concurrent.Executors;
public class Demo01 {
    public static void main(String[] args) {
        // Executors 工具类、3大方法
        // Executors.newSingleThreadExecutor();// 创建单个线程的线程池
        // Executors.newFixedThreadPool(5);// 创建一个固定大小的线程池
        // Executors.newCachedThreadPool();// 创建一个可伸缩的线程池
        // 单个线程的线程池
        ExecutorService threadPool =     
                                Executors.newSingleThreadExecutor();
        try {
            for (int i = 1; i < 100; i++) {
                // 使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(
                        Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

线程池:7大参数

源码分析:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService (
        new ThreadPoolExecutor(
            1, 
            1,
            0L, 
            TimeUnit.MILLISECONDS, 
            new LinkedBlockingQueue<Runnable>())); 
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        5, 
        5, 
        0L, 
        TimeUnit.MILLISECONDS, 
        new LinkedBlockingQueue<Runnable>()); 
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
        0, 
        Integer.MAX_VALUE, 
        60L, 
        TimeUnit.SECONDS, 
        new SynchronousQueue<Runnable>()); 
}
// 本质ThreadPoolExecutor() 
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小 
                          int maximumPoolSize, // 最大核心线程池大小 
                          long keepAliveTime, // 超时没有人调用就会释放 
                          TimeUnit unit, // 超时单位 
                          // 阻塞队列 
                          BlockingQueue<Runnable> workQueue, 
                          // 线程工厂:创建线程的,一般 不用动
                          ThreadFactory threadFactory,  
                          // 拒绝策略
                          RejectedExecutionHandler handle ) {
    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; 
}

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

手动创建一个线程池

因为实际开发中工具类Executors 不安全,所以需要手动创建线程池,自定义7个参数。

package com.haust.pool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// Executors 工具类、3大方法
// Executors.newSingleThreadExecutor();// 创建一个单个线程的线程池
// Executors.newFixedThreadPool(5);// 创建一个固定大小的线程池
// Executors.newCachedThreadPool();// 创建一个可伸缩的线程池
/**
 * 四种拒绝策略:
 *
 * new ThreadPoolExecutor.AbortPolicy() 
 * 银行满了,还有人进来,不处理这个人的,抛出异常
 *
 * new ThreadPoolExecutor.CallerRunsPolicy() 
 * 哪来的去哪里!比如你爸爸 让你去通知妈妈洗衣服,妈妈拒绝,让你回去通知爸爸洗
 *
 * new ThreadPoolExecutor.DiscardPolicy() 
 * 队列满了,丢掉任务,不会抛出异常!
 *
 * new ThreadPoolExecutor.DiscardOldestPolicy() 
 * 队列满了,尝试去和最早的竞争,也不会抛出异常!
 */
public class Demo01 {
    public static void main(String[] args) {
        // 自定义线程池!工作 ThreadPoolExecutor
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,// int corePoolSize, 核心线程池大小(候客区窗口2个)
                5,// int maximumPoolSize, 最大核心线程池大小(总共5个窗口) 
                3,// long keepAliveTime, 超时3秒没有人调用就会释,放关闭窗口 
                TimeUnit.SECONDS,// TimeUnit unit, 超时单位 秒 
                new LinkedBlockingDeque<>(3),// 阻塞队列(候客区最多3人)
                Executors.defaultThreadFactory(),// 默认线程工厂
                // 4种拒绝策略之一:
                // 队列满了,尝试去和 最早的竞争,也不会抛出异常!
                new ThreadPoolExecutor.DiscardOldestPolicy());  
        //队列满了,尝试去和最早的竞争,也不会抛出异常!
        try {
            // 最大承载:Deque + max
            // 超过 RejectedExecutionException
            for (int i = 1; i <= 9; i++) {
                // 使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(
                        Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

线程池:4种拒绝策略

在这里插入图片描述

/**
 * 四种拒绝策略:
 *
 * new ThreadPoolExecutor.AbortPolicy() 
 * 银行满了,还有人进来,不处理这个人的,抛出异常
 *
 * new ThreadPoolExecutor.CallerRunsPolicy() 
 * 哪来的去哪里!比如你爸爸 让你去通知妈妈洗衣服,妈妈拒绝,让你回去通知爸爸洗
 *
 * new ThreadPoolExecutor.DiscardPolicy() 
 * 队列满了,丢掉任务,不会抛出异常!
 *
 * new ThreadPoolExecutor.DiscardOldestPolicy() 
 * 队列满了,尝试去和最早的竞争,也不会抛出异常!
 */

小结和拓展

池的最大容量如何去设置!

了解:IO密集型,CPU密集型:(调优)

直接上代码:

package com.haust.pool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class Demo01 {
    public static void main(String[] args) {
        // 自定义线程池!工作 ThreadPoolExecutor
        // 最大线程到底该如何定义
        // 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高! 
        // 2、IO 密集型 > 判断你程序中十分耗IO的线程, 
        // 比如程序 15个大型任务 io十分占用资源!
        // IO密集型参数(最大线程数)就设置为大于15即可,一般选择两倍
        // 获取CPU的核数
        System.out.println(
            Runtime.getRuntime().availableProcessors());// 8核
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,// int corePoolSize, 核心线程池大小
                // int maximumPoolSize, 最大核心线程池大小 8核电脑就是8
                Runtime.getRuntime().availableProcessors(),
                3,// long keepAliveTime, 超时3秒没有人调用就会释放
                TimeUnit.SECONDS,// TimeUnit unit, 超时单位 秒 
                new LinkedBlockingDeque<>(3),// 阻塞队列(候客区最多3人)
                Executors.defaultThreadFactory(),// 默认线程工厂
                // 4种拒绝策略之一:
                // 队列满了,尝试去和 最早的竞争,也不会抛出异常!
                new ThreadPoolExecutor.DiscardOldestPolicy());  
        //队列满了,尝试去和最早的竞争,也不会抛出异常!
        try {
            // 最大承载:Deque + max
            // 超过 RejectedExecutionException
            for (int i = 1; i <= 9; i++) {
                // 使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(
                        Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/111682.html

(0)
Java光头强的头像Java光头强

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!