尚硅谷JUC

文章目录

  • 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这篇文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/19378.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hive语言

一、Hive的DDL语言&#xff08;数据库、数据表的增删改查操作) 二、Hive的DQL语言&#xff08;数据库查询语言&#xff09; 2.1Hive七子句 聚合函数&#xff1a;count()、sum()、max()、min()、avg()可以单独使用。(缩写&#xff1a;cs mm a) 2.1.1 分区查询与分区裁剪 SELEC…

女生学习软件测试怎么样?

在IT技术行业&#xff0c;女生学习还是有很大优势的。女生相较于男生更有耐心&#xff0c;包容性强&#xff0c;心思细腻&#xff0c;对细节把控更好&#xff0c;同时还能帮助团队男女平衡&#xff0c;活跃气氛。 编程是一个只要你肯学习就会有回报的行业&#xff0c;不论男生…

ResourceManager HA 原理

简介 为了解决 Yarn 中 ResourceManager 的单点故障问题&#xff0c;在 Hadoop 2.4 中新增了 ResourceManager HA 的能力&#xff0c; 该文章基于 Hadoop 3.1.1 进行讲解。 1.1. 名词定义 全称简称备注ResourceManagerRmZookeeperZK ResourceManager Ha 架构 ResourceMana…

前端工程化 搭建私有组件库 组件从开发到发布私有npm仓库的全过程

前言 基于Vue3.0 TS的组件从开发组件库到发布私有npm仓库的全过程 环境 这里列出本文所使用的环境版本 vue 3.0 vue/cli 4.5.9 nodeJs 14.15.1 npm 6.14.8 vue --version vue/cli 4.5.9 npm -v 6.14.8 node -v v14.15.1 步骤 创建项目 使用 vue-cli 创建一个 vue3 项目&a…

mysql的高级查询语句

1.本文前言 数据库是用来存储数据&#xff0c;更新&#xff0c;查询数据的工具&#xff0c;而查询数据是一个数据库最为核心的功能&#xff0c;数据库是用来承载信息&#xff0c;而信息是用来分析和查看的。所以掌握更为精细化的查询方式是很有必要的。本文将围绕数据的高级查…

C++类和对象(中)

目录 1.类的6个默认成员函数 2.构造函数 2.1构造函数的概念 2.2构造函数的重载 2.3默认构造函数 2.4总结 3.析构函数 3.1析构函数的概念 3.2编译器自动生成的析构函数会做那些事情呢&#xff1f; 3.3析构函数的析构顺序 4.拷贝构造函数&#xff08;复制构造函数&am…

用ChatGPT三分钟免费做出数字人视频- 提升自媒体魅力

用ChatGPT三分钟免费做出数字人视频- 提升自媒体魅力 一、ChatGPT产生文案二、腾讯智影网站三、选择一个2D数字人四、粘贴文本五、编辑自定义&#xff0c;合成六、资源七、其他数字人平台推荐八、生成视频预览 本教程收集于&#xff1a;AIGC从入门到精通教程汇总 操作指引 Ch…

哪个牌子的电视盒子好用?罗鹏数码盘点2023电视盒子排名

电视机资源少、卡顿&#xff0c;配置不足的时候只需要安装一台电视盒子就可以解决这些问题&#xff0c;不需要花费大价钱更换电视机。那么&#xff0c;你知道哪个牌子的电视盒子好用吗&#xff1f;今天罗鹏就来详细聊聊这个话题&#xff0c;分享2023最新电视盒子排名。 一&…

gitlab服务器发送邮件配置

1.修改gitlab的配置文件&#xff1a; vim /etc/gitlab/gitlab.rb 这里具体的gitlab.rb文件所在路径需要根据实际的来 找到如下图所示的部分&#xff0c;放开注释&#xff0c;修改配置&#xff0c;此处我用的发件邮箱是QQ邮箱&#xff0c;所以域名配置都是qq.com&#xff0c;…

图表控件Stimulsoft 2023.2 带来极致深色主题, 一起来看看还有哪些亮点?

Stimulsoft Reports 是一款报告编写器&#xff0c;主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署&#xff0c;如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等&#xff0c;在你的应用程序中嵌入报告设计器…

clickhouse的嵌套数据结构Tuple、Array与Nested类型介绍和使用示例

文章目录 Tuple类型Array类型Nested类型使用示例单独使用Tuple数组嵌套 Array(Tuple)Nested类型 生产使用&#xff1a;分组查询 Tuple类型 Tuple是ClickHouse数据库中的一种数据类型&#xff0c;它允许在一个字段中存储由不同数据类型组成的元组(tuple)。元组可以包含任意数量…

快速了解C语言的基本元素

C语言是一种编程语言&#xff0c;和其它语言一样&#xff0c;也定义了自己的语法和词汇。学习C语言&#xff0c;首先要学习C语言的词汇&#xff0c;再学习C语言的语法规则&#xff0c;然后由词汇构成语句&#xff0c;由语句构成源程序&#xff0c;源程序也称为源代码或代码&…

ChatGPT :国内免费可用 ChatGPT +Midjourney绘图

前言 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI 研发的聊天机器人程序 &#xff0c;于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过理解和学习人类的语言来…

【MySQL】绪论 MySQL工作环境

文章目录 实验内容实验步骤实验内容 MySQL命令MySQL 的启动与关闭MySQL 管理备份和还原数据库navicat工具使用实验步骤 1. MySQL命令 (1)查看MySQL基本命令 (2)查看MySQL版本信息 2. MySQL的启动与关闭 (1)启动MySQL服务器 (2)测试服务器启动成功 (3)合法用

stream笔记

1、 创建流stream 1.1、 Stream 的操作三个步骤 1.2、 stream中间操作 1.2.1 、 limit、skip、distinct 1.2.2、 map and flatMap 1.2.3、 sort 自然排序和定制排序 1.3、 add and andAll difference: 1.4、 终止操作 1.4.1、 allmatch、anyMatch、noneMatch、max、min…

前端开发中,定位bug的几种常用方法

目录 第一章 前言 第二章 解决bug的方法 2.1 百度 2.2 有道翻译 2.3 debugger 2.4 console.log 日志打印 2.5 请求体是否携带参数 2.6 注释页面渲染代码 2.7 其他 第三章 尾声 备注&#xff1a;该文章只是本人在工作/学习中常用的几种方法&#xff0c;如果有不对大家…

朋友去华为面试,轻松拿到30K的Offer,羡慕了......

最近有朋友去华为面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

第四十六章 Unity 布局(上)

学习了UI元素的使用&#xff0c;并不能构建出一个完整的UI界面&#xff0c;我们需要使用一些方法将这些UI元素按照“设计稿”的效果&#xff0c;将其摆放到对应的位置上。如何摆放这些UI元素&#xff0c;就是我们需要讲的“布局”&#xff0c;当然这需要借助一些布局组件来完成…

毕业论文相关

毕业论文参考文献和Word保存 一、Word中出现[7-9]多个文献的引用 在正文中选中参考文献角标&#xff0c;右击选择“切换域代码”&#xff0c;参考文献角标[7][8][9]变为{ REF _Ref98345319 \r \h * MERGEFORMAT }{ REF _Ref98345321 \r \h * MERGEFORMAT }{ REF _Ref99390603…

第5章 负载均衡

第5章 负载均衡 5.1 proxy_pass详解 在nginx中配置proxy_pass代理转发时&#xff0c;如果在proxy_pass后面的url加/&#xff0c;表示绝对根路径&#xff1b;如果没有/&#xff0c;表示相对路径&#xff0c;把匹配的路径部分也给代理走。 假设下面四种情况分别用 http://192.…