尚硅谷JUC

news2025/1/31 3:16:30

文章目录

  • 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() {
            @Override
            public void run() {
                //调用买票的方法
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"AA").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                //调用买票的方法
                for(int i=0;i<40;i++){
                    ticket.sale();
                }
            }
        },"BB").start();
        new Thread(new Runnable() {
            @Override
            public 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();

    //创建三个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();

        }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() {
                @Override
                public 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() {
                @Override
                public 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() {
                @Override
                public 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() {
                @Override
                public 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
------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();
    }
}

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();
       // synchronized
        Object o = new Object();
        new Thread(()->{
            synchronized(o) {
                System.out.println(Thread.currentThread().getName()+" 外层");

                synchronized (o) {
                    System.out.println(Thread.currentThread().getName()+" 中层");

                    synchronized (o) {
                        System.out.println(Thread.currentThread().getName()+" 内层");
                    }
                }
            }

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

  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 {

    @Override
    public void run() {

    }
}


class MyClass2 implements Callable{

    @Override
    public 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) {
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier =
                new CyclicBarrier(NUMBER,()->{
                    System.out.println("*****集齐7颗龙珠就可以召唤神龙");
                });

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

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.coloradmin.cn/o/521263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Windows线程开发】Windows线程同步技术

我们在上一章节中讲解了关于Windows的线程基础&#xff0c;相信大家已经对线程有了基本的概念。这一章节中&#xff0c;我们来讲讲线程同步技术&#xff0c;包括加锁技术&#xff08;原子锁和互斥体&#xff09;和事件&#xff0c;信号灯。 文章目录 一.原子锁二.互斥体三.事件…

SpringCloud-网关 Gateway

网关Gateway 一、网关初识二、网关的使用1.创建项目并引入依赖2.编写网关配置3.启动服务并测试 三.查看网关路由规则列表四.路由服务的负载均衡五.断言和过滤1.断言Predicate1.1.The Path Route Predicate Factory(路径断言工厂&#xff09;1.2.The After Route Predicate Fact…

【27】linux进阶——rpm软件包的管理

大家好&#xff0c;这里是天亮之前ict&#xff0c;本人网络工程大三在读小学生&#xff0c;拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识&#xff0c;希望能提高自己的技术的同时&#xff0c;也可以帮助到大家 另外其它专栏请关注&#xff1a; 锐捷数通实验&…

基于SSM+MySQL的支教管理系统

目录 1、系统介绍 2、系统功能展示 3、运行环境 4、运行效果 5、运行配置 6、下载地址 1、系统介绍 本系统是基于SSM(SpringSpringMVCMyBatis)框架开发的支教管理系统&#xff0c;系统共有超级管理员&#xff0c;系统管理员&#xff0c;支教学校&#xff0c;志愿者这四大…

我的新书上架了!

talk is cheap&#xff0c;show you my book&#xff01; 新书《从0开始学ARM》终于在各大平台上架了&#xff01;&#xff01; 一、关于本书 1. 本书主要内容 ARM体系架构是目前市面上的主流处理器体系架构&#xff0c;在手机芯片和嵌入式芯片领域&#xff0c;ARM体系架构…

前端架构师-week5-命令行交互原理

目录 加餐&#xff1a;命令行交互原理 学习路径 readline 源码分析 如何开发命令行交互列表 实现原理 架构图 本章学习路径和学习目标 readline 的使用方法和实现原理 高能&#xff1a;深入讲解 readline 键盘输入监听实现原理 秀操作&#xff1a;手写 readline 核心实…

MySQL---控制流函数、窗口函数(序号函数、开窗聚合函数、分布函数、前后函数、头尾函数、其他函数)

1. 控制流函数 格式 解释 案例 IF(expr,v1,v2) 如果表达式 expr 成立&#xff0c;返回结果 v1&#xff1b;否则&#xff0c;返回结果 v2。 SELECT IF(1 > 0,正确,错误) ->正确 IFNULL(v1,v2&#xff09; 如果 v1 的值不为 NULL&#xff0c;则返回 v1&#xff…

JVM垃圾收集器(一)

目录 1、如何考虑 GC 2、如何确定一个对象“死去” 3、分代收集理论 4、垃圾回收算法 5、HotSpot的算法实现细节 1、如何考虑 GC 垃圾收集&#xff08;Garbage Collection&#xff0c;GC&#xff09;的历史比Java更久远&#xff0c;1960年诞生于MIT。 GC 需要考虑的三件事…

UNIAPP实战项目笔记72 提交订单到选择支付方式的前后端交互

UNIAPP实战项目笔记72 提交订单到选择支付方式的前后端交互 思路 购物车确认订单,跳转到订单确认界面确认支付后清除购物车对应id的数据 实例截图 清空购物车数据后 代码 前端代码 order.js export default{state:{// 订单号orderNumber:},getters:{},mutations:{initOr…

数字化转型浪潮下,如何选择适合企业的低代码平台

近日&#xff0c;艾瑞咨询发布了《数字新生态&#xff1a;中国低代码厂商发展白皮书》&#xff08;以下简称“报告”&#xff09;&#xff0c;在该报告中&#xff0c;艾瑞咨询对中国当前的低代码市场进行了非常细致的解构&#xff0c;并针对当前企业数字化转型&#xff0c;对低…

电阻传感器工作原理

金属随着温度变化&#xff0c;其电阻值也发生变化。 对于不同金属来说&#xff0c;温度每变化一度&#xff0c;电阻值变化是不同的&#xff0c;而电阻值又可以直接作为输出信号。 电阻共有两种变化类型 正温度系数 温度升高 阻值增加 温度降低 阻值减少 负温度系数 温…

【一起啃书】《机器学习》第七章 贝叶斯分类器

文章目录 第七章 贝叶斯分类器7.1 贝叶斯决策论7.2 极大似然估计7.3 朴素贝叶斯分类器7.4 半朴素贝叶斯分类器7.5 贝叶斯网7.6 EM算法 第七章 贝叶斯分类器 7.1 贝叶斯决策论 对分类任务来说&#xff0c;在所有相关概率都已知的理想情形下&#xff0c;贝叶斯决策论考虑如何基于…

k8s学习-CKS真题-利用AppArmor进行应用行为限制

目录 题目环境搭建解题模拟题参考 题目 Task 在 cluster 的工作节点 node02 上&#xff0c;实施位于 /etc/apparmor.d/nginx_apparmor 的现有 APPArmor 配置文件。 编辑位于 /cks/KSSH00401/nginx-deploy.yaml 的现有清单文件以应用 AppArmor 配置文件。 最后&#xff0c;应用清…

UE4及Airsim安装时遇到的问题及解决办法

UE4及Airsim安装时遇到的问题及解决办法 目录 UE4及Airsim安装时遇到的问题及解决办法前言UE4下载慢解决方法 Airsim编译过程中提示&#xff1a;无法打开包括文件: “Eigen/Dense”: No such file or directory [D:\software\Visual_studio2022\2022Community\AirSim\Air解决办…

C语言——运算符和表达式

所谓表达式就是指由运算符、运算量和标点符号组成的有效序列&#xff0c;其目的是说明一个计算过程。表达式可以独立成语句&#xff1a;表达式; 运算符按功能分为&#xff1a;算术运算、赋值运算、关系运算、逻辑运算、位运算以及其他运算符 1. 算术运算符&#xff1a; - * / %…

项目部署 | Linux安装Git和Maven

知识目录 一、写在前面✨二、安装Git&#x1f495;2.1 yum安装git2.2 新建Git仓库2.3 拉取仓库代码 三、安装Maven&#x1f495;3.1 上传Maven压缩包并解压3.2 配置环境变量3.3 设置本地仓库3.4 设置中央仓库 四、总结撒花&#x1f60a; 一、写在前面✨ 大家好&#xff01;我是…

二叉树的递归遍历与迭代遍历(图示)

文章目录 前言1. 二叉树的递归遍历&#xff08;一入递归深似海&#xff0c;从此offer是路人&#xff09;1.1 [前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/)1.2 [中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/)1.3 [后序…

实验一 Python基础编程

实验一 Python基础编程 只为给原因学习编程的同学提供一个思路&#xff0c;让编程更简单&#xff01;&#xff01;&#xff01; 本博主擅长整理粉丝的私信&#xff01;只要你有需求就可以告诉博主&#xff01;博主可以帮你解决并发表&#xff01; 一、实验学时 2学时 二、实…

docker发布到dockerhub报错denied: requested access to the resource is denied

docker发布到dockerhub报错denied: requested access to the resource is denied 解决方案 修改发布的镜像的REPOSITORY为自己的账户名镜像&#xff0c;比如我的用户名是luobotoutou123。docker tag tomcat02:1.0 luobotoutou123/tomcat02:1 然后发布镜像 到dockerhub远程仓库…

学习杂记 2023.5.13 单词背诵

目录 鼠标上的DPI是什么&#xff1f; 鼠标上的DPI是什么&#xff1f; DPI是英文Dots Per Inch的缩写&#xff0c;意思是每英寸点数。在计算机中&#xff0c;DPI通常用于描述指针设备&#xff08;例如鼠标&#xff09;的精度。在鼠标上&#xff0c;DPI指的是鼠标移动时指针在屏…