尚硅谷JUC

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值