JUC高并发编程

JUC概述

JUC简介
     
  在Java中 线程部分是一个重点 , JUC也是关于线程的.JUC就是java.util.concurrent工具包的简称.这是一个处理线程的工具包JDK 1.5出现的

进程和线程
        ·进程:
是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配和调度的基本单位,是操作系统结构的基础 在当代面向线程设计的计算机结构中 进程是线程的容器 程序是指令、数据及其组织形式的藐视,进程是程序的实体.是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配和调度的基本单位,是操作系统结构的基础.程序是指令、数据及其组织形式的描述,进程是程序的实体
        ·线程:是操作系统能够进行运算调度的最小单位 它被包含在进程之中 是进程中的实际运作单位 一条线程指的是进程中一个单一顺序的控制流 一个进程中可以并发多个线程 每条线程并行执行不同的任务
        总结:
                ①进程:
指在系统中正在运行的一个应用程序 程序一旦运行就是进程
        进程-资源分配的最小单位
                ②线程:系统分配处理器时间资源的基本单元 或者说进程之内独立执行的一个单元执行流
        线程-程序执行的最小单位

线程状态枚举类
        NEW,(新建)
        
RUNNABLE,(准备就绪)
        BLOCKED,(阻塞)
        WAITING
,(不见不散)
        TIMED_WAITING
,(过时不候)
        TERMINATED
;(终结)

wait/sleep 的区别
       
①sleep是Thread的静态方法     wait是Object的方法 任何对象都能调用
        ②sleep不会释放锁 它也不需要占用锁 wait会释放锁 但调用它的前提是当前

线程占有锁(即代码要在synchronized中)
        ③它们都可以被interrupted方法中断

并发和并行
        1.串行模式
                
串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能 运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。
        串行是一次只能取得一个任务,并执行这个任务

        2.并行模式
                
并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模 式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上 则依赖于多核 CPU。

        3.并发
             
   并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可 以同时运行或者多指令可以同时运行。

        tips:
                
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
                        例子:春运抢票 电商秒杀...
                并行:多项工作一起执行,之后再汇总
                        例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

管程(监视器、锁)
     
  是一种同步机制 保证同一个时间 只有线程访问被保护的数据或代码
        管程(monitor)
是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同 一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行
        JVM 中同步是基于进入和退出管程(monitor)
对象实现的,每个对象都会有一个管程 (monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁
        执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程

用户线程和守护线程

        1.用户线程:平时用到的普通线程,自定义线程
        2.守护线程:
运行在后台,是一种特殊的线程,比如垃圾回收
        3.当主线程结束后,
用户线程还在运行,JVM 存活
        4.如果没有用户线程,
都是守护线程,JVM 结束       

Lock接口

        Synchronized
                
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
                        1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;
                        2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
        如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
        1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
        2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
        那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep 方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。

Lock
        Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能。

 

Lock和synchronized 有以下几点不同:
        1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized是内置的语言实现;
        2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象因此使用 Lock 时需要在 finally 块中释放锁;
        3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断;4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
        5. Lock 可以提高多个线程进行读操作的效率。
        在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。

线程间通信
 

        线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模 型来实现的。我们来基本一道面试常见的题目来分析
        场景---两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

 

代码:synchronized 方案

import java.util.concurrent.locks.ReentrantLock;

public class TestJUC {
   public static void main(String[] args) {
      Share share = new Share();
      new Thread(()->{
         for (int i = 1; i <=10; i++) {
            try {
               share.jiayi();
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      },"AA").start();

      new Thread(()->{
         for (int i = 1; i <=10; i++) {
            try {
               share.jianyi();
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      },"BB").start();
   }
}
class Share{
   //初始值
   private int number = 0;
   //+1的方法
   public synchronized void jiayi() throws InterruptedException {
      if(number!=0){
         this.wait();//若number值不是0 等待
      }
      number++;
      System.out.println(Thread.currentThread().getName() + "::" + number);
      //通知其他线程
      this.notifyAll();
   }
   //-1的方法
   public synchronized void jianyi() throws InterruptedException {
      if(number!=1){
         this.wait();//若number值不是1 等待
      }
      number--;
      System.out.println(Thread.currentThread().getName() + "::" + number);
      //通知其他线程
      this.notifyAll();
   }
}

虚假唤醒: AA、BB、CC、DD 谁先执行不一定 比如AA先执行 通知其他线程时 可能BB抢到了也可能CC或者DD抢到了

        对于某一个参数的方法 实现终端和虚假唤醒是可能的 而且此方法应始终在循环中使用  即: 判断条件放到if中会产生虚假唤醒

import java.util.concurrent.locks.ReentrantLock;

public class TestJUC {
   public static void main(String[] args) {
      Share share = new Share();
      new Thread(()->{
         for (int i = 1; i <=10; i++) {
            try {
               share.jiayi();
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      },"AA").start();

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

      new Thread(()->{
         for (int i = 1; i <=10; i++) {
            try {
               share.jianyi();
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      },"DD").start();
   }
}
class Share{
   //初始值
   private int number = 0;
   //+1的方法
   public synchronized void jiayi() throws InterruptedException {
      if(number!=0){
         this.wait();//若number值不是0 等待
      }
      number++;
      System.out.println(Thread.currentThread().getName() + "::" + number);
      //通知其他线程
      this.notifyAll();
   }
   //-1的方法
   public synchronized void jianyi() throws InterruptedException {
      if(number!=1){
         this.wait();//若number值不是1 等待
      }
      number--;
      System.out.println(Thread.currentThread().getName() + "::" + number);
      //通知其他线程
      this.notifyAll();
   }
}


        通常的解决方法是将if改为while


代码:Lock 方案

        一般来说,当等待Condition时,允许发生“虚假的唤醒”,作为对底层平台语义的让步。这对大多数应用程序几乎没有实际影响,因为应该始终在循环中等待Condition,测试正在等待的状态谓词。实现可以自由消除虚假唤醒的可能性,但建议应用程序程序员始终假设它们可能发生,因此始终在循环中等待。  即:为了防止虚假唤醒 也需要用while而不是if

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

public class ThreadDemo {
    public static void main(String[] args) {
        share s = new share();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                s.jiayi();
            }
        },"AA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                s.jianyi();
            }
        },"BB").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                s.jiayi();
            }
        },"CC").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                s.jianyi();
            }
        },"DD").start();
    }
}
class share{
    //初始值
    private int number = 0;
    //创建Lock
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void jiayi(){
        lock.lock();
        try {
            while (number!=0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "::" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void jianyi(){
        lock.lock();
        try {
            while (number!=1){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "::" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}


线程间定制化通信
      
  
A 线程打印 5 次 ,B 线程打印 10 次 ,C 线程打印 15 次 ,按照此顺序循环 10 轮

 代码:      

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

public class ThreadDemo {
    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();
                    }
                }
            },"A").start();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}
class ShareResource{
    //定义标志位
    private int flag = 1;//A1 B2 C3
    private Lock lock  = new ReentrantLock();
    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    // 参数:第几轮
    public void print5(int loop) throws InterruptedException {
        lock.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();//通知B
        }finally {
            lock.unlock();
        }

    }

    // 参数:第几轮
    public void print10(int loop) throws InterruptedException {
        lock.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();//通知B
        }finally {
            lock.unlock();
        }

    }

    // 参数:第几轮
    public void print15(int loop) throws InterruptedException {
        lock.lock();
        try {
            while (flag!=3){
                c3.await();
            }
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "::" + i + "    轮数:" + loop);
            }
            flag=1;//先修改标志位
            c1.signal();//通知B
        }finally {
            lock.unlock();
        }

    }

}

集合的线程安全    

以ArrayList为例   

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

/*
ArrayList、LinkedList 线程不安全
 */
public class ThreadEx {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        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();
        }
    }
}

 list.sout  发生的问题   为并发修改的问题 即 内容可能没放进去 或者有多个线程要放

解决方案-Vector(比较古老的方案)

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

/*
ArrayList、LinkedList 线程不安全
 */
public class ThreadEx {
    public static void main(String[] args) {
        List<String> vector = new Vector<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                vector.add(UUID.randomUUID().toString().substring(0,1));
                System.out.println(vector);
            },String.valueOf(i)).start();
        }
    }
}

 add 方法被 synchronized 同步修辞,线程安全!因此没有并发异常

解决方案-Collections (比较古老的方案)

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

/*
ArrayList、LinkedList 线程不安全
 */
public class ThreadEx {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        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();
        }
    }
}

Collections 提供了方法 synchronizedList 保证 list 是同步线程安全的
 

解决方案-CopyOnWriteArrayList(重要 JUC中提供的类 写时复制技术)

        首先我们对 CopyOnWriteArrayList 进行学习,其特点如下:
                它相当于线程安全的 ArrayList。和 ArrayList 一样,它是个可变数组;但是和
ArrayList 不同的时,它具有以下特性:
        1. 它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多
于可变操作,需要在遍历期间防止线程间的冲突。
        2. 它是线程安全的。
        3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove()
等等)的开销很大。
        4. 迭代器支持 hasNext(), next()等不可变操作,但不支持可变 remove()等操作。
        5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代
器时,迭代器依赖于不变的数组快照
代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/*
ArrayList、LinkedList 线程不安全
 */
public class ThreadEx {
    public static void main(String[] args) {
        List<String> list =  new CopyOnWriteArrayList<>();
        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();
        }
    }
}
原因分析 ( 重点 ):== 动态数组与线程安全 ==
        下面从“动态数组”和“线程安全”两个方面进一步对 CopyOnWriteArrayList 的原理进行说明。
“动态数组”机制
        · 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做 CopyOnWriteArrayList 的原因
        · 由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,
效率比较高。
“线程安全”机制
        · 通过 volatile 和互斥锁来实现的。
        · 通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证。
        · 通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”, 再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥锁”,就达到了保护数据的目的。
 
以HashSet和HashMap为例  
        ①HashSet
               同样出现 java.util.ConcurrentModificationException
                解决方案: 
Set<String> hashSet = new CopyOnWriteArraySet<>();
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;


public class ThreadEx {
    public static void main(String[] args) {
        HashSet<String> hashSet =  new HashSet<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                hashSet.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(hashSet);
            },String.valueOf(i)).start();
        }
    }
}

②HashMap 
                
同样出现 java.util.ConcurrentModificationException
                解决方案: 
Map<String,String> hashSet = new ConcurrentHashMap<>()

import java.util.*;

public class ThreadEx {
    public static void main(String[] args) {
        Map<String,String> hashSet =  new HashMap<>();
        for (int i = 0; i < 10; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                hashSet.put(key,UUID.randomUUID().toString().substring(0,8));
                System.out.println(hashSet);
            },String.valueOf(i)).start();
        }
    }
}


多线程锁
 

代码演示:        


import java.util.concurrent.TimeUnit;

class Phone {

    public static synchronized void sendSMS() throws Exception {
        //停留4秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }

    public void getHello() {
        System.out.println("------getHello");
    }
}

/**
 * @Description: 8锁
 *
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail

2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail

3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS

4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS

5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail

6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail

7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS

8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS

 */

public class Lock_8 {
    public static void main(String[] args) throws Exception {

        Phone phone = new Phone();
        Phone phone2 = new Phone();

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

        Thread.sleep(100);

        new Thread(() -> {
            try {
               // phone.sendEmail();
               // phone.getHello();
                phone2.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "BB").start();
    }
}

分析:
       ①标准访问 短信邮件都是普通synchronized 、②停4秒在短信方法内
                都是先短信后邮件 synchronized 锁的是当前的对象this
        因为"AA").start();后Thread.sleep(100);   所以一定是先AA后BB
       ③新增普通的hello方法
                没有synchronized
 所以先执行
       ④两部手机
                两个不同的对象 不是同一把锁 先邮件 因为短信等了四秒 
       ⑤、⑥两个静态同步方法(1个、2个手机)
                先短信后邮件 static synchronized  锁的是Class 当前类的大class
       ⑦、⑧1个静态同步方法,1个普通同步方法(1个、2个手机)
                先邮件后短信  静态锁的是Class 非静态锁的是this 不是同一把锁 先邮件 短信等了四秒 

结论 :
        一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中的 一个 synchronized 方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法锁的是当前对象 this ,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized 方法加个普通方法后发现和同步锁无关换成两个对象后,不是同一把锁了,情况立刻变化。synchronized 实现同步的基础: Java 中的每一个对象都可以作为锁。
具体表现为以下 3 种形式。
        对于普通同步方法,锁是当前实例对象。
        对于静态同步方法,锁是当前类的 Class 对象。
        对于同步方法块,锁是 Synchonized 括号里配置的对象
        当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁, 可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

公平锁和非公平锁
    
    private ReentrantLock lock = new ReentrantLock();//无参构造器 默认非公平锁
        private ReentrantLock lock = new ReentrantLock(true);//公平锁   
        private ReentrantLock lock = new ReentrantLock(false);//非公平锁          
        非公平锁:
缺点线程饿死    优点效率高
                举例:比如卖票问题 可能出现AA一个人卖了100张 BB CC 一张都没卖
        公平锁: 缺点效率相对低 优点雨露均沾 各个线程都有机会

可重入锁(也称 递归锁)
        sychronized(隐式 自动加锁解锁)和Lock(显示 手动加锁解锁)都是可重入锁
代码:sychronized

public class SyncLockDemo {

    public  synchronized  void add(){
        add();
    }
    public static void main(String[] args) {

        new SyncLockDemo().add();
        Object o = new Object();
        new Thread(()->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + "    outside");
                synchronized (o){
                    System.out.println(Thread.currentThread().getName() + "    midside");
                    synchronized (o){
                        System.out.println(Thread.currentThread().getName() + "    inside");
                    }
                }
            }
        },"A").start();
    }
}

add报错 synchronized不报错因为其是可重复锁

代码:lock

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

public class SyncLockDemo {

    public static void main(String[] args) {

        Object o = new Object();
        Lock lock = new ReentrantLock();
        new Thread(()->{
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " O");
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + " I");
                }finally {
                    lock.unlock();
                }
            }finally {
                lock.unlock();
            }

        },"A").start();
    }
}

死锁
        概念:
两个或两个以上进程在执行过程中 因为争夺资源而造成一种互相等待的现象 若没有外力干涉 他们无法再执行下去
        产生死锁的原因:  ①系统资源不足 ②进行运行推进顺序不合适 ③资源分配不当
死锁的一个例子:

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

public class SyncLockDemo {
    static Object A = new Object();
    static Object B = new Object();
    public static void main(String[] args) {
        new Thread(()->{
            synchronized (A){
                System.out.println(Thread.currentThread().getName() + "      持有A 视图B");
                synchronized (B){
                    System.out.println(Thread.currentThread().getName() + "获取B");
                }
            }
        },"A").start();
        new Thread(()->{
            synchronized (B){
                System.out.println(Thread.currentThread().getName() + "      持有B 视图A");
                synchronized (A){
                    System.out.println(Thread.currentThread().getName() + "获取A");
                }
            }
        },"B").start();
    }
}

        验证是否死锁
                ①jps
                ②jstack jvm自带堆栈跟踪工具

创建线程的多种方式
        ①继承Thread类
        ②实现Runnable接口
        ③实现Callable接口
        ④线程池

 

③实现Callable接口
        • 为了实现 Runnable,需要实现不返回任何内容的 run()方法,而对于Callable,需要实现在完成时返回结果的 call()方法。
        • call()方法可以引发异常,而 run()则不能。
        • 为实现 Callable 而必须重写 call 方法
        • 不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable
代码:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            return 1233;
        });
        new Thread(futureTask2,"z").start();
        System.out.println(futureTask2.get());
    }
}

或者

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

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        new Thread(new MyThread2(),"BB").start();  报错
        MyThread2 myThread2 = new MyThread2();
        FutureTask<Integer> futureTask = new FutureTask<>(myThread2);
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}


class MyThread2 implements Callable{

    @Override
    public Integer call() throws Exception {
        return 200;
    }
}

tips:代码实践发现实现接口才能写成Lambda,继承抽象类不行

 FutureTask原理 
             
  1.老师上课 口渴了 去买票不合适 讲课线程继续
                        单开启线程 找同学帮老师买水
                                把水买回来 需要的时候直接喝(get)

                2.4个同学 1同学1+2+3+4+5        2同学算10+....+50
                   3同学 算60+...62        4同学100+200
                        2同学计算量比较大  FutureTask单开线程给2同学计算 先汇总1 3 4 最后等
                2同学计算完成 再统一汇总

                3考试 先做会做的题  最后做不会做的
                        只计算一次

JUC的辅助类

减少计数 CountDownLatch
        CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句。
        • CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
        • 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
        • 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行

        演示:六个人陆续离开后 班长才关锁门 下面这种写法是不对的 应该是1-6走了锁门

public class Demo {
    //六个人陆续离开后 班长才关锁门
    public static void main(String[] args){
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"号同学离开了教室");
            },String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName()+"锁门");
    }
}

        解决办法:使用CountDownLatch辅助类

import java.util.concurrent.CountDownLatch;

public class Demo {
    //六个人陆续离开后 班长才关锁门
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"号同学离开了教室");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
//等待 若计数器值等于0 再往下走
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"锁门");
    }
}


循环栅栏 CyclicBarrier
 
        CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。
        可以将 CyclicBarrier 理解为加 1 操作

例子:集齐7颗龙珠 召唤神龙
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

public class Demo {
    //集齐7颗龙珠
    private static final int NUMBER = 7;
    public static void main(String[] args) {
        //两个参数 第一个是固定值 第二个是达到固定值后 要做什么
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
            System.out.println("YESYES");
        });

        for (int i = 1; i <=7 ; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"star(s)");
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

信号灯 Semaphore
        Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证.使用 acquire 方法获得许可证,release 方法释放许可
例子:抢车位, 6 部汽车 3 个停车位
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class Demo {
   //抢车位, 6 部汽车 3 个停车位
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);

        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));
                    System.out.println(Thread.currentThread().getName() + "号车离开了车位-------------!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

读写锁
        

      读写锁介绍

        现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那 么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源, 就不应该允许其他线程对该资源进行读和写的操作了。针对这种场景,JAVA 的并发包提供了读写锁 ReentrantReadWriteLock, 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称 为排他锁

 代码:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Myche myche = new Myche();
        //创建线程放数据
        for (int i = 1; i <=5 ; i++) {
            int num = i;
            new Thread(()->{
                myche.put(num+"",num+"");
            },String.valueOf(i)).start();
        }
      
        //创建线程取数据
        for (int i = 1; i <=5 ; i++) {
            int num = i;
            new Thread(()->{
                myche.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}
class Myche{
    private volatile Map<String,Object> map = new HashMap<>();

    public void put(String key,Object value) {
        System.out.println(Thread.currentThread().getName() + "正在写操作" + key);

        //暂停一下
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "写完了" + key);
    }


    public Object get(String key){
        Object result = null;
        System.out.println(Thread.currentThread().getName() + "正在取操作" + key);

        //暂停一下
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "取完了" + key);
        return result;
    }
}

 这么写 很明显有问题 应该是先写 写完了再取
用读写锁优化后:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Myche myche = new Myche();
        //创建线程放数据
        for (int i = 1; i <=5 ; i++) {
            int num = i;
            new Thread(()->{
                myche.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        //创建线程取数据
        for (int i = 1; i <=5 ; i++) {
            int num = i;
            new Thread(()->{
                myche.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}
class Myche{

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private volatile Map<String,Object> map = new HashMap<>();

    public void put(String key,Object value) {
        //添加写锁
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "正在写操作" + key);
        //暂停一下
        try {
            TimeUnit.MICROSECONDS.sleep(300);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "写完了" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }


    }


    public Object get(String key){

        readWriteLock.readLock().lock();
        Object result = null;
        System.out.println(Thread.currentThread().getName() + "正在取操作" + key);
        //暂停一下
        try {
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "取完了" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {

            readWriteLock.readLock().unlock();
        }


        return result;
    }
}

写锁是独占锁  读锁是共享锁 
        一个线程可以被多个读线程访问 或者可以被一个写线程访问 但是不能同时存在读写线程,读写互斥,读读共享

锁降级
        ·
为了提高数据的可见性 将写入锁 降级为读锁   读锁不能升级为写错
        ·遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

  public static void main(String[] args) throws InterruptedException {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

        ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

        //锁降级
        writeLock.lock();
        //1.获取写锁
        System.out.println("111111");
        //2.获取读锁
        readLock.lock();
        System.out.println("read");

        //释放写锁
        writeLock.unlock();

        //释放读锁
        readLock.unlock();

    }

    意思就是写操作后 写锁还没有unlock  也可以获取到读锁  但是如果先获取读锁 再获取写锁就不行了 因为读锁不能升级为 写锁 
        eg:增删改 写
             查 读
     

阻塞队列      

        ①当队列是空的,从队列中获取元素的操作将会被阻塞
        ②当队列是满的,从队列中添加元素的操作将会被阻塞
        ③试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
        ④试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多
个元素或者完全清空,使队列变得空闲起来并后续新增
 
常用的队列主要有以下两种:
        • 先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。
从某种程度上来说这种队列也体现了一种公平性
        • 后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发
生的事件(栈)
        在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起
的线程又会自动被唤起

 
BlockingQueue
        
        Concurrent 包中,BlockingQueue 很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。

BlockingQueue的常见方法
方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(time,unit)
检查element()peek()不可用不可用
tips:
        ①抛出异常
               
1)当阻塞队列满时 再往队列里add插入元素会抛IllegalStateException:Queue full
                2)当阻塞队列空时 再从队列里remove移除元素会抛NoSuchElementException
        ②特殊值
               
1)插入方法 成功true 失败false
                2)移除方法 成功返回出队列的元素 队列里没有就返回null
        ③一直阻塞
               
1)当阻塞队列满时 生产者线程继续往队列里put元素 队列会一直阻塞生产者线程直
        到put数据or相应中断退出
                2)当阻塞队列空时 消费者线程试图从队列里take元素 队列会一直阻塞消费者线程直到队
        列可用
        ④超时退出

                当阻塞队列满时 队列会阻塞生产者线程一定时间 超过限时后生产者线程会退出
        
·ArrayBlockingQueue(常用)
       
由数组结构组成的有界阻塞队列

·LinkedBlockingQueue(常用)
        
由链表结构组成的有界阻塞队列(大小默认值为Integer.MAX_VALUE)
·DelayQueue
        使用优先级队列实现的延迟无界阻塞队列

· PriorityBlockingQueue
       
支持优先级排序的无界阻塞队列
·SynchronousQueue
       
 不存储元素的阻塞队列,也即单个元素的队列。
·LinkedTransferQueue
      
  由链表组成的无界阻塞队列。
·LinkedBlockingDeque
        
由链表组成的双向阻塞队列

代码:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        //创建阻塞队列
        BlockingQueue<String> blockingDeque = new ArrayBlockingQueue<>(3);
        //第一组
        System.out.println(blockingDeque.add("A"));
        System.out.println(blockingDeque.add("B"));
        System.out.println(blockingDeque.add("C"));

        System.out.println(blockingDeque.remove());
        System.out.println(blockingDeque.remove());
        System.out.println(blockingDeque.remove());

        //第二组
        System.out.println(blockingDeque.offer("a"));
        System.out.println(blockingDeque.offer("b"));
        System.out.println(blockingDeque.offer("c"));

        System.out.println(blockingDeque.poll());
        System.out.println(blockingDeque.poll());
        System.out.println(blockingDeque.poll());

        //第三组 不会报错 会出现阻塞
        blockingDeque.put("Q");
        blockingDeque.put("W");
        blockingDeque.put("3");

        System.out.println(blockingDeque.take());
        System.out.println(blockingDeque.take());
        System.out.println(blockingDeque.take());

        //第四组 阻塞 超时后会出退出
        System.out.println(blockingDeque.offer("a"));
        System.out.println(blockingDeque.offer("b"));
        System.out.println(blockingDeque.offer("c"));
        System.out.println(blockingDeque.offer("d",2L, TimeUnit.SECONDS));
    }
}

        

线程池

                线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
                例子: 10 年前单核 CPU 电脑,假的多线程,像马戏团小丑玩多个球,CPU 需要来回切换。 现在是多核电脑,多个线程各自跑在独立的 CPU 上,不用切换效率高。


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

        线程池在构造前(new操作)是初始状态,一旦构造完成线程池就进入了执行状态RUNNING。严格意义上讲线程池构造完成后并没有线程被立即启动,只有进行“预启动”或者接收到任务的时候才会启动线程。但是线程池是出于运行状态,随时准备接受任务来执行。

它的主要特点为:
        • 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
        • 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
        • 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
        • Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor Executors
ExecutorService ,ThreadPoolExecutor 这几个类

​​​​​​​

常用的七个参数(重点)
        • corePoolSize 线程池的核心线程数
        • maximumPoolSize 能容纳的最大线程数
        • keepAliveTime 空闲线程存活时间
        • unit 存活的时间单位
        • workQueue 存放提交但未执行任务的队列
        • threadFactory 创建线程的工厂类
        • handler 等待队列满后的拒绝策略

 
线程池中,有三个重要的参数,决定影响了拒绝策略
        1.corePoolSize - 核心线程数,也即最小的线程数。
        2.workQueue - 阻塞队列 。
        3.maximumPoolSize - 最大线程数
        当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。
        总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略


 
拒绝策略(重点)
        CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
        AbortPolicy: 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
        DiscardPolicy: 直接丢弃,其他啥都没有
        DiscardOldestPolicy: 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入


 
线程池的种类与创建

       newCachedThreadPool(常用)

作用 :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
 
特点 :
        • 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
        • 线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
        • 当线程池中,没有可用线程,会重新创建一个线程​​​​​​​
 
场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args){
        //一个线程池 可扩容线程 比如模拟现在银行有5个窗口 但是有10个顾客
        ExecutorService executorService = Executors.newCachedThreadPool();

        //10个顾客请求
        try {
            for (int i = 1; i <=10 ; i++) {
                //执行
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "在办理业务");
                });
            }
        } catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            executorService.shutdown();
        }
    }
}
 
        ②newFixedThreadPool(常用)
作用 :创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池
中的线程将一直存在。

特征:
线程池中的线程处于一定的量,可以很好的控制线程的并发量
线程可以重复被使用,在显示关闭之前,都将一直存在
• 超出一定量的线程被提交时候需在队列中等待
 
场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args){
        //一个线程池 N个线程 比如模拟现在银行有5个窗口 但是有10个顾客
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        //10个顾客请求
        try {
            for (int i = 1; i <=10 ; i++) {
                //执行
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "在办理业务");
                });
            }
        } catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            executorService.shutdown();
        }
    }
}
        ③newSingleThreadExecutor(常用)
作用
:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool 不同,可保证无需重新配置此方法所返回的执行程序即
可使用其他的线程。
 
特征: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此 执行​​​​​​​
 
场景: 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个 线程的场景
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args){
        //一个线程池 1个线程 比如模拟现在银行有1个窗口 但是有10个顾客
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        //10个顾客请求
        try {
            for (int i = 1; i <=10 ; i++) {
                //执行
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "在办理业务");
                });
            }
        } catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            executorService.shutdown();
        }
    }
}

 
        ④newScheduleThreadPool(了解)
作用: 线程池支持定时以及周期性执行任务,创建一个 corePoolSize 为传入参数,最大线程数为整形的最大数的线程池**
 
特征:
1)线程池中具有指定数量的线程,即便是空线程也将保留
2)可定时或者延迟执行线程活动

场景: 适用于需要多个后台线程执行周期任务的场景



 
        ⑤newWorkStealingPool
        jdk1.8 提供的线程池,底层使用的是 ForkJoinPool 实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用 cpu 核数的线程来并行执行任务

场景: 适用于大耗时,可并行执行的场景​​​​​​​        
线程池的底层工作原理

1. 在创建了线程池后,线程池中的线程数为零
2. 当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:
        2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
        2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
        2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
        2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3. 当一个线程完成任务时,它会从队列中取下一个任务来执行
4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
        4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
        4.2 所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

注意事项(重要)
 
        1. 项目中创建多线程时,使用常见的三种线程池创建方式,单一、可变、定长都有一定问题,原因是 FixedThreadPool 和 SingleThreadExecutor 底层都是用LinkedBlockingQueue 实现的,这个队列最大长度为 Integer.MAX_VALUE,容易导致 OOM。所以实际生产一般自己通过 ThreadPoolExecutor 的 7 个参数,自定义线程池
        2. 创建线程池推荐适用 ThreadPoolExecutor 及其 7 个参数手动创建
                · corePoolSize 线程池的核心线程数
                · maximumPoolSize 能容纳的最大线程数
                · keepAliveTime 空闲线程存活时间
                · unit 存活的时间单位
                · workQueue 存放提交但未执行任务的队列
                · threadFactory 创建线程的工厂类
                · handler 等待队列满后的拒绝策略
3. 为什么不允许适用不允许 Executors.的方式手动创建线程池,如下图
import java.util.concurrent.*;

public class Demo {
    public static void main(String[] args){
      //自定义线程池
        ExecutorService threadPool =
                new ThreadPoolExecutor(2,5,2L,
                        TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.AbortPolicy());


        //处理10个请求
        try{
            for (int i = 1; i <=10 ; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            threadPool.shutdown();
        }

    }
}

Fork/Join

        Fork/Join 它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。Fork/Join 框架要完成两件事情:

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

         ​​​​​​​1. 任务分割:首先 Fork/Join 框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
         2. 执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程 分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里, 启动一个线程从队列里取数据,然后合并这些数据。
        ​​​​​​​在 Java 的 Fork/Join 框架中,使用两个类完成上述操作
        • ForkJoinTask:我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。
                该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了两个子类:
                a.RecursiveAction:用于没有返回结果的任务
                b.RecursiveTask:用于有返回结果的任务
        • ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool 来执行
        • RecursiveTask: 继承后可以实现递归(自己调自己)调用的任务​​​​​​​

Fork/Join 框架的实现原理
        ForkJoinPool 由 ForkJoinTask 数组和 ForkJoinWorkerThread 数组组成,
        ForkJoinTask 数组负责将存放以及将程序提交给 ForkJoinPool,而
        ForkJoinWorkerThread 负责执行这些任务。

 Fork 方法



代码:  比如计算1+2+3+……+100 相加两数差不超过10 超过10就拆分

import java.security.Key;
import java.security.KeyStore;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyTask myTask = new MyTask(0,100);
        //创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> submit = forkJoinPool.submit(myTask);

        //获取合并后的结果
        Integer integer = submit.get();
        System.out.println(integer);
        //关闭池对象
        forkJoinPool.shutdown();
    }
}
class MyTask extends RecursiveTask<Integer>{
    //拆分差值不能超过10
    private static final Integer VALUE = 10;

    private int left;//拆分开始值
    private int right;//拆分结束值
    private int result;//返回结果

    //带参构造
    public MyTask(int left, int right) {
        this.left = left;
        this.right = right;
    }

    //拆分和合并的过程
    @Override
    protected Integer compute() {
        //判断相加两个数值是否大于10
        if((right-left)<=VALUE){
            //相加
            for (int i = left; i <=right ; i++) {
                result+=i;
            }
        }else {
            //继续拆分
            int mid = (left+right)/2;
            //拆左边
            MyTask myTask1 = new MyTask(left,mid);
            //拆右边
            MyTask myTask2 = new MyTask(mid+1,right);
            //调用方法拆分
            myTask1.fork();
            myTask2.fork();
            //合并结果
            result = myTask1.join()+myTask2.join();
        }
        return result;
    }
}

  

      

 异步回调   
        

同步与异步
        同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。

        异步:异步是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了,即异步是我们发出的一个请求,该请求会在后台自动发出并获取数据,然后对数据进行处理,在此过程中,我们可以继续做其他操作,不管它怎么发出请求,不关心它怎么处理数据

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //异步调用没有返回值
        CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName() + "1");
        });
        completableFuture1.get();
        //异步调用有返回值
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName() + "2");
            return 1024;
        });
        completableFuture2.whenComplete((t,u)->{
            System.out.println(t+ "t");//方法的返回值
            System.out.println(u+ "u");//异常信息
        }).get();


    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
大学生在线租房平台管理系统按照操作主体分为管理员和用户。管理员的功能包括报修管理、报修评价管理、字典管理、房东管理、房屋管理、房屋收藏管理、房屋留言管理、房屋租赁管理、租房论坛管理、公告信息管理、留言板管理、用户管理、管理员管理。用户的功能等。该系统采用了Mysql数据库,Java语言,Spring Boot框架等技术进行编程实现。 大学生在线租房平台管理系统可以提大学生在线租房平台信息管理问题的解决效率,优化大学生在线租房平台信息处理流程,保证大学生在线租房平台信息数据的安全,它是一个非常可靠,非常安全的应用程序。 管理员权限操作的功能包括管理公告,管理大学生在线租房平台信息,包括房屋管理,培训管理,报修管理,薪资管理等,可以管理公告。 房屋管理界面,管理员在房屋管理界面中可以对界面中显示,可以对房屋信息的房屋状态进行查看,可以添加新的房屋信息等。报修管理界面,管理员在报修管理界面中查看报修种类信息,报修描述信息,新增报修信息等。公告管理界面,管理员在公告管理界面中新增公告,可以删除公告。公告类型管理界面,管理员在公告类型管理界面查看公告的工作状态,可以对公告的数据进行导出,可以添加新公告的信息,可以编辑公告信息,删除公告信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值