尚硅谷JUC

news/2024/11/28 9:48:43/

文章目录

  • 1. 什么是JUC
    • 1.1 JUC简介
    • 1.2 进程和线程基本概念
    • 2.1 Synchronized
      • 2.1.1 Synchronized关键字
      • 2.1.2 synchronized实现三个线程卖30张票
    • 2.2 Lock
      • 2.2.1 什么是Lock
      • 2.2.2 使用Lock实现买票功能
      • 2.2.3 两者的区别
  • 3. 线程间通信及定制化通信
    • 3.1 使用synchronized实现线程之间的通信
    • 3.2 虚假唤醒问题:(我称虚假唤醒为偷渡,是不是很形象)
    • 3.3 线程间通信及定制化通信
  • 4.集合的线程安全
    • 4.1 ArrayList集合线程不安全演示
    • 4.2 解决方案
    • 4.3 HashSet线程不安全
    • 4.4. HashMap线程不安全
  • 5. 多线程锁
    • 5.1 演示锁的八种情况
    • 5.2 公平锁和非公平锁
    • 5.3. 可重入锁
    • 5.4 死锁
  • 6.Callable接口
    • 6.1 Callable接口概述
    • 6.2 Callable使用方式
    • 6.3 FutureTask未来任务类
  • 7. JUC强大的辅助类
    • 7.1. 减少计数CountDownLatch
    • 7.2. 循环栅栏CyclicBarrier
    • 7.3. 信号灯Semaphore
  • 8. ReentrantReadWriteLock读写锁
    • 1. 乐观锁和悲观锁
    • 8.2 读写锁及表锁和行锁
    • 8.3 示例
    • 8.4 读写锁的演变

1. 什么是JUC

1.1 JUC简介

  1. java并发编程包中的一些工具类,这些工具类可以更加方便实现并发编程操作。
  2. JUC就是java.util.concurrent工具包的简称。这是一个处理线程的工具包,从JDK1.5开始的

1.2 进程和线程基本概念

  1. 进程和线程
  • 进程: 系统进行资源分配和调度的基本单位,是操作系统结构的基础
  • 线程:是操作系统能够进行运算和调度的最小单位,他被包含在进程中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程;并行执行不同的任务。
  • 总结来说:进程指在系统中正在运行的一个运用程序,程序一旦运行就是进程,进程——资源分配的最小单位;线程:系统分配处理器时间资源的基本单位,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。一个进程可以有多个线程。
  1. wait和 sleep 的区别
  • sleep是Thread的静态方法,wait是Object方法,任何实例都能调用
  • sleep不会释放锁,它也不需要占用锁。wait会释放锁,调用它的前提是当前线程占有锁(即代码要在synchronize中)
  • 他们都可以被interrupted方法中断
  1. 并发和并行的概念区别?
  • 并发:同一时刻多个线程在访问同一个资源,多个线程对一个点 例子:春运抢票 电商秒杀…
  • 并行:多项工作一起执行,之后再汇总 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中
  1. 什么是管程?
  • Moniters,也称为监视器。在操作系统中叫监视器;在java中叫锁。
  • 是一种同步机制,保证同一个时间,只有一个线程能去访问被保护数据或者代码
  • jvm同步基于进入和退出,使用管程对象实现的,在临界区加锁操作,这个过程通过管程对象进行管理。
  1. 用户线程和守护线程的区别
  • 定义不同

    • 用户线程:平时使用到的线程均为用户线程。
    • 守护线程:用来服务用户线程的线程,例如垃圾回收线程。
  • 作用区别

    • 守护线程和用户线程的区别主要在于Java虚拟机是后存活。
    • 用户线程:当任何一个用户线程未结束,Java虚拟机是不会结束的。
    • 守护线程:如果只剩守护线程未结束,Java虚拟机结束。

2.1 Synchronized

2.1.1 Synchronized关键字

Synchronized是Java中的关键字,是一种同步锁。他修饰的对象有以下几种

  1. 修饰一个代码块,被修饰的代码块是同步代码块,其作用的范围值大括号括起来的代码,作用的对象是调用这个代码块的对象
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
  3. 修饰一个静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象
  4. 修饰一个类,其作用范围是Synchronized后面括号括起来的部分,作用的对象是这个类的多少对象

2.1.2 synchronized实现三个线程卖30张票

//创建一个资源类 定义相关的属性和方法
class Ticket{//票数private  int number = 30;//操作方法 卖票public synchronized void sale(){if(number > 0){System.out.println(Thread.currentThread().getName()+":卖出"+(number--)+"剩下:"+number);}}}public class SaleTicker {public static void main(String[] args) {//创建对象Ticket ticket = new Ticket();//创建三个线程new Thread(new Runnable() {@Overridepublic void run() {//调用买票的方法for(int i=0;i<40;i++){ticket.sale();}}},"AA").start();new Thread(new Runnable() {@Overridepublic void run() {//调用买票的方法for(int i=0;i<40;i++){ticket.sale();}}},"BB").start();new Thread(new Runnable() {@Overridepublic void run() {//调用买票的方法for(int i=0;i<40;i++){ticket.sale();}}},"CC").start();}
}

2.2 Lock

2.2.1 什么是Lock

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

  1. Lock 与的 Synchronized 区别
  • Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内 置特性。Lock 是一个类,通过这个类可以实现同步访问;
  • Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户 去手动释放锁,当 synchronized 方法或synchronized 代码块执行完之后, 系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如 果没有主动释放锁,就有可能导致出现死锁现象。

2.2.2 使用Lock实现买票功能

创建多线程的步骤(上)

  1. 创建资源类,在资源类创建属性和操作方法。
  2. 创建多个线程,调用资源类的操作方法。
//创建一个资源类 定义相关的属性和方法
class LTicket{//票数private  int number = 30;//操作方法 卖票private final ReentrantLock lock = new ReentrantLock();//买票的方法public void sale(){try {lock.lock();if (number > 0) {System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+"剩余:"+number);}}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}
}public class LSaleTicket {public static void main(String[] args) {//创建多个线程,调用资源类的操作方法LTicket lTicket = new LTicket();new Thread(()->{for(int i=0;i<40;i++){lTicket.sale();}},"AA").start();new Thread(()->{for(int i=0;i<40;i++){lTicket.sale();}},"BB").start();new Thread(()->{for(int i=0;i<40;i++){lTicket.sale();}},"CC").start();}}

2.2.3 两者的区别

Lock 和 synchronized 有以下几点不同:

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

3. 线程间通信及定制化通信

3.1 使用synchronized实现线程之间的通信

  1. wait和notify方法
  • wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
  • notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
  1. 创建多线程的步骤中

创建线程步骤(中部,下部):

  1. 第一步:创建资源类,在资源类创建属性和操作方法。
  2. 第二步:在资源类操作方法中进行以下操作
    ​ 1. 判断
    ​ 2.干活
    ​ 3. 通知
  3. 创建多个线程,调用资源类的操作方法
  1. 实现一个线程+1一个线程-1的操作
package com.atguigu.sync;
//创建资源类  定义属性和方法class Share{//初始值private int number = 0;// +1的方法public synchronized  void incr() throws InterruptedException {// 第二步 判断 通知 干活if(this.number != 0){ //判断是否为0  不是0就等待this.wait();}//如果是0 就进行+1操作number ++;System.out.println(Thread.currentThread().getName()+"::"+number);//通知其他线程this.notifyAll();}// -1 的方法public synchronized  void decr() throws InterruptedException {if(number != 1){this.wait();}number --;System.out.println(Thread.currentThread().getName()+"::"+number);//通知其他线程this.notifyAll();}
}public class ThreadDemo1 {public static void main(String[] args) {Share share = new Share();new Thread(()->{for(int i=0;i<10;i++){try {share.incr();//+1} catch (InterruptedException e) {e.printStackTrace();}}},"AA").start();new Thread(()->{for(int i=0;i<10;i++){try {share.decr();//-1} catch (InterruptedException e) {e.printStackTrace();}}},"BB").start();}}

3.2 虚假唤醒问题:(我称虚假唤醒为偷渡,是不是很形象)

  1. 在多个生成者和消费者的情况下会出现虚假唤醒问题。
  2. wait()方法特点:在哪里睡,就在哪里醒。
  3. 防止刚刚等待,然后被唤醒,然后又抢到锁的情况。
  4. 倘若在这种情况用if包裹wait()方法,在哪里睡,在那醒,结果就可以逃脱判断筛选(偷渡!!!)
  5. 其实在jdk的API文档中就说明了该问题,官方推荐使用whlie对wait()进行包裹

存在的问题

  • 然而,我还有个问题,难道在只有一个生产者和一个消费者的情况下,就不会出现虚假唤醒的可能吗?
    ​ 当执行到wait()方法时,会让此线程等待,并释放锁。它一直在这行代码等待。所以不往下执行。所以并不会出行自己唤醒自己的可能,所以也就不可能出行虚假唤醒问题。

  • 那为什么多个生产者和多个消费者就会出现呢虚假唤醒问题呢?
    ​ 当锁连续被3个不同生产者或者3个消费者抢到,并且第4次被消费者或生产者抢到,以生产者为例:
    ​ A. 生产者1抢到锁,生产+1,代码执行结束,释放锁。
    ​ B. 生产者2抢到锁,进入if判断,等待并释放锁。
    ​ C. 生产者1抢到锁,进入if判断,等待并释放锁。
    ​ D. 消费者1抢到锁,生产-1,唤醒所有。此时库存为0
    ​ E. 生产者1抢到锁,生产+1,唤醒所有。
    ​ F. 生产者2抢到锁,生产+1,唤醒所有。
    ​ 当然还有其他种情况。

3.3 线程间通信及定制化通信

  1. Lock接口下有一个实现类,后两个是接口:可重入锁,读锁,写锁。
  2. Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition()方法。
  3. 用notify()通知时,JVM会随机唤醒某个等待的线程, 使用Condition类可以进行选择性通知,Condition比较常用的两个方法:
    • await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
    • signal()用于唤醒一个等待的线程。
    • signalAll()唤醒所有线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;//创建资源类 定义属性和方法
class ShareResource{//定义标志位private int flag = 1;// 1 AA 2 BB  3CC//创建Lock锁private Lock lock = new ReentrantLock();//创建三个conditionprivate 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();}finally {//释放锁lock.unlock();}}//打印10次public void print10(int loop) throws InterruptedException {lock.lock();try {//判断while ( flag != 2){c2.await();//等待}for(int i=0;i<=10;i++){System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);}flag = 3;c3.signal();}finally {lock.unlock();}}public void print15(int loop) throws InterruptedException {lock.lock();try {while ( flag != 3 ){c3.await();}for(int i=0;i<=15;i++){System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);}flag = 1;c1.signal();}finally {lock.unlock();}}}public class ThreadDemo3 {public static void main(String[] args) {System.out.println("开始运行代码");ShareResource shareResource = new ShareResource();new Thread(()->{for(int i=0;i<10;i++){try {shareResource.print5(i);} catch (InterruptedException e) {e.printStackTrace();}}},"AA").start();new Thread(()->{for(int i=0;i<10;i++){try {shareResource.print10(i);} catch (InterruptedException e) {e.printStackTrace();}}},"BB").start();new Thread(()->{for(int i=0;i<10;i++){try {shareResource.print15(i);} catch (InterruptedException e) {e.printStackTrace();}}},"CC").start();}
}

4.集合的线程安全

4.1 ArrayList集合线程不安全演示

public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合ArrayList<String> list = new ArrayList<>();for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}

4.2 解决方案

  1. 解决方案-Vector(jdk1.0方案,太老了)
    ​ 在接口List下除了ArrayList实现类外,还有一个Vector实现类。
public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合//        ArrayList<String> list = new ArrayList<>();Vector<String> list = new Vector<>();for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}
  1. 解决方案-Collections(这种方式也很老,这两种方案用的都不多,用的最多的是JUC里的解决方案)
    借助工具类Collections下的静方法synchronizedList(List list)
public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合//        ArrayList<String> list = new ArrayList<>();//创建vector解决
//        Vector<String> list = new Vector<>();
//        Collections解决List<String> list = Collections.synchronizedList(new ArrayList<String>());for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}
  1. 解决方案-CopyOnWriteArrayList
    这个类也被称为“写时复制技术”
public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合//        ArrayList<String> list = new ArrayList<>();//创建vector解决
//        Vector<String> list = new Vector<>();
//        Collections解决
//        List<String> list = Collections.synchronizedList(new ArrayList<String>());CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}

源码分析

public boolean add(E e) {//可重入锁final ReentrantLock lock = this.lock;//上锁lock.lock();try {//1.得到内容Object[] elements = getArray();//2.得到旧数组长度int len = elements.length;//拷贝到一个新数组,以扩展一个元素的形式拷贝到新数组Object[] newElements = Arrays.copyOf(elements, len + 1);//将内容写到新数组,len是旧数组的长度,他他作为下标是应该是扩展一位的意思。newElements[len] = e;//新数组覆盖之前的旧数组setArray(newElements);return true;} finally {//解锁lock.unlock();}
}
//妙啊!!!

4.3 HashSet线程不安全

类似上面操作就行,解决方案 CopyOnWriteArraySet

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

4.4. HashMap线程不安全

解决方案 ConcurrentHashMap

        //演示HashMap
//        Map<String,String> map = new HashMap<>();Map<String,String> map = new ConcurrentHashMap<>();for (int i = 0; i <30; i++) {String key = String.valueOf(i);new Thread(()->{//向集合添加内容map.put(key,UUID.randomUUID().toString().substring(0,8));//从集合获取内容System.out.println(map);},String.valueOf(i)).start();

5. 多线程锁

5.1 演示锁的八种情况

演示代码

package com.atguigu.sync;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
------sendEmail2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS8 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();}
}

8锁只不过是这几种情况而已,并不是官方定义的。
总结三个知识点:

  1. 对于普通同步方法,锁是当前实例对象。
  2. 对于静态同步方法,锁是当前类的 Class 对象。
  3. 对于同步方法块,锁是 Synchonized 括号里配置的对象

5.2 公平锁和非公平锁

看源码秒懂!

  1. 非公平锁:private Lock lock = new ReentrantLock();当可重入锁的构造函数无参,或者参数为false时,为非公平锁
  2. 公平锁:相对的为true时,为公平锁。
  3. 源码:
public ReentrantLock() {sync = new NonfairSync();
}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
  1. 举例理解:

​ 1)a,b,c3个人卖30张票,非公平锁时就是,一个人把票卖完了;公平锁就是3个人都能卖到票。都有钱赚。
​ 2)公交车找座位:非公平锁直接坐那。公平锁就是还要礼貌的问候:”这里有人吗?“。。。

  1. 非公平锁和公平锁的优缺点

    1. 非公平锁:
    • ​ 优点:效率高
    • ​ 缺点:容易造成线程饿死
    1. 公平锁:
    • ​ 优点:阳光普照,都能吃上饭
    • ​ 缺点:效率相对低

5.3. 可重入锁

  1. 可重入锁也称为递归锁。
  2. synchronized 和 Lock都是可重入锁。前者隐式,后者显式。
  3. 那到底什么是可重入锁?说实话这两节可我没太听懂,但是这篇文章讲清楚了。
  4. 什么是可重入锁:什么是 “可重入”,可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
  5. synchronized源码案例:
package com.atguigu.sync;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;//可重入锁
public class SyncLockDemo {public synchronized void add() {add();}public static void main(String[] args) {// new SyncLockDemo().add();// synchronizedObject o = new Object();new Thread(()->{synchronized(o) {System.out.println(Thread.currentThread().getName()+" 外层");synchronized (o) {System.out.println(Thread.currentThread().getName()+" 中层");synchronized (o) {System.out.println(Thread.currentThread().getName()+" 内层");}}}},"t1").start();}
}
  1. Lock源码案例:
    ​ 这个要注意,ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样,容易报死锁或者栈溢出异常。
public class SyncLockDemo {public synchronized void add() {add();}public static void main(String[] args) {//Lock演示可重入锁Lock lock = new ReentrantLock();//创建线程new Thread(()->{try {//上锁lock.lock();System.out.println(Thread.currentThread().getName()+" 外层");try {//上锁lock.lock();System.out.println(Thread.currentThread().getName()+" 内层");}finally {//释放锁lock.unlock();}}finally {//释放做lock.unlock();}},"t1").start();//创建新线程new Thread(()->{lock.lock();System.out.println("aaaa");lock.unlock();},"aa").start();}
}

5.4 死锁

在这里插入图片描述
示例

import java.util.concurrent.TimeUnit;/*** 演示死锁*/
public class DeadLock {//创建两个对象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");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (b) {System.out.println(Thread.currentThread().getName()+" 获取锁b");}}},"A").start();new Thread(()->{synchronized (b) {System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (a) {System.out.println(Thread.currentThread().getName()+" 获取锁a");}}},"B").start();}
}

6.Callable接口

6.1 Callable接口概述

我们可以通过创建Thread类或者使用Runnable创建线程,但是Runnable缺少的一项功能是当线程终止时(即run()完成时),我们无法获取线程返回的结果,为了支持此功能,java中提供了Callable接口

Callable和Runnable的区别

  1. Runnable没有返回值二Callable有返回值
  2. Runnable不能抛出异常二Runnable可以抛出异常
  3. Runnable实现的是run方法而Callable实现的是call方法

6.2 Callable使用方式

  1. 为了实现 Runnable,需要实现不返回任何内容的 run()方法,而对于 Callable,需要实现在完成时返回结果的 call()方法。
  2. call()方法可以引发异常,而 run()则不能。
  3. 为实现 Callable 而必须重写 call 方法。
  4. 不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable。

6.3 FutureTask未来任务类

在这里插入图片描述

  1. FutureTask概述与原理,为什么叫未来任务来类?
    ​ 举例:4个同学,1同学 1+2…5, 2同学 10+11+12…50, 3同学 60+61+62, 4同学 100+200
    ​ 第2个同学计算量比较大,

​ FutureTask单开启线程给2同学计算,先汇总 1 3 4 ,最后等2同学计算位完成,统一汇总

  1. 代码示例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyClass1 implements Runnable {@Overridepublic void run() {}
}class MyClass2 implements Callable{@Overridepublic Integer call() throws Exception {return 200;}
}public class Demo1 {public static void main(String[] args) throws ExecutionException, InterruptedException {//Runnable创建线程
//        new Thread(new MyClass1(),"AA").start();FutureTask futureTask1 = new FutureTask<>(new MyClass2());//lamd表达式FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {System.out.println(Thread.currentThread().getName()+"come in callable");return 1024;});//创建一个线程new Thread(futureTask2,"AA").start();new Thread(futureTask1,"BB").start();while (!futureTask1.isDone()){System.out.println("wait....");}while (!futureTask2.isDone()){System.out.println("wait....");}//调用FutureTask的get方法System.out.println("futureTask1:"+futureTask2.get());System.out.println("futureTask2:"+futureTask1.get());System.out.println(Thread.currentThread().getName()+"over...");}
}

7. JUC强大的辅助类

7.1. 减少计数CountDownLatch

CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行 减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法 之后的语句。

  1. CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这 些线程会阻塞
  2. 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程 不会阻塞)
  3. 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行

​ 场景: 6 个同学陆续离开教室后值班同学才可以关门。

public class CountDownDemo {public static void main(String[] args) throws InterruptedException {//创建CountDownLatch 并设置初始值CountDownLatch countDownLatch = new CountDownLatch(6);for(int i=0;i<6;i++){new Thread(()->{System.out.println(Thread.currentThread().getName()+"离开教师");//计数减一countDownLatch.countDown();},String.valueOf(i)).start();}countDownLatch.await();System.out.println("人走光了");}
}

7.2. 循环栅栏CyclicBarrier

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

场景: 集齐 7 颗龙珠就可以召唤神龙

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {//创建固定值private static final int NUMBER = 7;public static void main(String[] args) {//创建CyclicBarrierCyclicBarrier cyclicBarrier =new CyclicBarrier(NUMBER,()->{System.out.println("*****集齐7颗龙珠就可以召唤神龙");});//集齐七颗龙珠过程for (int i = 1; i <=7; i++) {new Thread(()->{try {System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");//等待cyclicBarrier.await();} catch (Exception e) {e.printStackTrace();}},String.valueOf(i)).start();}}
}

7.3. 信号灯Semaphore

Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线 程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方 法获得许可证,release 方法释放许可。

场景: 抢车位, 6 部汽车 3 个停车位

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;public class SemaphoreDemo {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);for(int i=0;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();}}
}

8. ReentrantReadWriteLock读写锁

1. 乐观锁和悲观锁

在这里插入图片描述

8.2 读写锁及表锁和行锁

在这里插入图片描述

  1. 表锁:把整张表锁住;
    ​ 行锁:把一行锁住。

  2. 什么是读写锁?
    ​ JAVA 的并发包提供了读写锁 ReentrantReadWriteLock, 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称 为排他锁

  3. 读写锁:

  • 前者共享锁,可能发生死锁;后者独占锁,也可能发生死锁。
  • 读可以一起读,写只能一个人写。即别人读的时候不能写,别人写的时候也不能写。这是发生死锁的根本。
  • 无论是读还是写,都有可能发生死锁。
  • 读锁:读的时候可以进行写的操作,1在修改时,要等2的读完成后;而2的修改,要等1读完后,可能会发生死锁。
  • 写锁:线程1和2在同时操作两个两个记录,线程1要等2操作完第二条记录,线程2要等1操作完第一条记录。互相等待,产生死锁。

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

8.3 示例

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;//资源类
class MyCache{//创建Map集合private volatile Map<String,Object> map = new HashMap<>();//创建读写锁对象private ReadWriteLock rwLock = new ReentrantReadWriteLock();//向Map 集合放数据public void put(String key,Object value){//添加写锁Lock lock = rwLock.writeLock();lock.lock();//暂停一会try {System.out.println(Thread.currentThread().getName()+"正在写操作"+key);TimeUnit.MICROSECONDS.sleep(300);//放数据map.put(key,value);System.out.println(Thread.currentThread().getName()+"写完了"+key);} catch (InterruptedException e) {e.printStackTrace();}finally {//释放写锁lock.unlock();}}//取数据public Object get(String key){Lock lock = rwLock.readLock();lock.lock();Object result = null;try {System.out.println(Thread.currentThread().getName() + "正在读取操作" + key);//暂停一会TimeUnit.MICROSECONDS.sleep(300);result = map.get(key);System.out.println(Thread.currentThread().getName() + "取完了" + key);}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}return result;}
}public class ReadWriteLockDemo {public static void main(String[] args) {MyCache myCache = new MyCache();//创建线程放数据for(int i=0;i<5;i++){final int num = i;new Thread(()->{myCache.put(num+"",num+"");},String.valueOf(i)).start();}//创建线程取数据for (int i =0;i<5;i++){final int num = i;new Thread(()->{Object o = myCache.get(num + "");System.out.println(o);},String.valueOf(i)).start();}}
}

8.4 读写锁的演变

在这里插入图片描述
线程池可以参考http://t.csdn.cn/tHzoS这篇文章


http://www.ppmy.cn/news/68973.html

相关文章

Nginx总结

目录 Nginx介绍 Nginx的作用 反向代理 项目架构 实战&#xff1a;访问nginx服务器反向代理到另一台虚拟机上的tomcat服务器 负载均衡 项目架构 实战&#xff1a;访问nginx服务器&#xff0c;是否反向代理到集群中的任意一台tomcat服务器&#xff0c;停止一台tomcat服务器&…

Redisson锁的分析

文章目录 一、分布式锁概念1、实现思路2、出现死锁问题如何避免死锁问题 锁过期和释放当前不属于当前线程的锁解决不是加锁线程释放锁锁的过期时间如何解决 二、Redisson分布式锁Redis的部署方式对锁的影响集群模式Redlock实现高可靠的分布式锁 三、代码分析V1代码无锁V2代码单…

基于AT89C51单片机的电子密码锁设计与仿真

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/87779960?spm=1001.2014.3001.5503 源码获取 主要内容: 设计一个单片机电子密码锁,根据输入的数值判断是否正确,正确显示密码正确;错误时报警器发出蜂鸣。由单片机系统…

RabbitMQ养成记 (6. spingboot 集成 rabbitMQ,生产者/消费者)

Springboot集成 搞springboot的那群人 不喜欢造轮子&#xff0c;就喜欢搞各种集成。 首先创建一个springboot项目&#xff1a; 之前我们在方法中 创建工厂类配置&#xff0c; 现在直接在application.yml 中配置即可&#xff1a; spring:rabbitmq:host: **********username:…

Nvidia技术路线和卷积神经网络介绍

1.Nvidia技术路线概述 2.卷积神经网络介绍 软硬件平台 目的:用卷积神经网络(CNNs)将车前部摄像头捕捉到的原始像素图映射为汽车的方向操控命令。 训练:这套端到端学习系统使用了NVIDIA DevBox, 用Torch 7进行训练。 操作:一台 NVIDIA DRIVE PX 自动驾驶汽车计算…

Ch3.栈、队列、数组

文章目录 1.栈1.栈的基本概念2.栈的性质卡特兰数栈的出栈顺序3.栈的实现1.顺序栈 (栈的顺序存储实现)共享栈2.链栈(栈的链式存储实现)4.栈的应用(1) 括号匹配(2) 表达式求值(中缀表达式→后缀表达式)(3)递归(4)进制转换2.队列1.队列的概念2.队列的性质3.队列的实现(1)顺序队…

滨州软件著作权申请

各省、自治区、市版权局负责本辖区作者或者其他作权人作品的登记。国家版权局负责外国、台湾、香港、澳门作者或者其他作权人作品的登记。受国家版权局委托&#xff0c;中国版权保护中心负责外国、台湾、香港、澳门作者或者其他作权人作品的登记。 作者或者其他享有作权的公民所…

微服务 - Redis缓存 · 数据结构 · 持久化 · 分布式 · 高并发

一、分布式解决 Session 的问题 在单站点中&#xff0c;可以将在线用户信息存储在Session中&#xff0c;随时变更获取信息&#xff1b;在多站点分布式集群如何做到Session共享呢&#xff1f;架设一个Session服务&#xff0c;供多服务使用。 频繁使用的数据存在DB端&#xff0…