文章目录
- 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简介
- java并发编程包中的一些工具类,这些工具类可以更加方便实现并发编程操作。
- JUC就是java.util.concurrent工具包的简称。这是一个处理线程的工具包,从JDK1.5开始的
1.2 进程和线程基本概念
- 进程和线程
- 进程: 系统进行资源分配和调度的基本单位,是操作系统结构的基础
- 线程:是操作系统能够进行运算和调度的最小单位,他被包含在进程中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程;并行执行不同的任务。
- 总结来说:进程指在系统中正在运行的一个运用程序,程序一旦运行就是进程,进程——资源分配的最小单位;线程:系统分配处理器时间资源的基本单位,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。一个进程可以有多个线程。
- wait和 sleep 的区别
- sleep是Thread的静态方法,wait是Object方法,任何实例都能调用
- sleep不会释放锁,它也不需要占用锁。wait会释放锁,调用它的前提是当前线程占有锁(即代码要在synchronize中)
- 他们都可以被interrupted方法中断
- 并发和并行的概念区别?
- 并发:同一时刻多个线程在访问同一个资源,多个线程对一个点 例子:春运抢票 电商秒杀…
- 并行:多项工作一起执行,之后再汇总 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中
- 什么是管程?
- Moniters,也称为监视器。在操作系统中叫监视器;在java中叫锁。
- 是一种同步机制,保证同一个时间,只有一个线程能去访问被保护数据或者代码
- jvm同步基于进入和退出,使用管程对象实现的,在临界区加锁操作,这个过程通过管程对象进行管理。
- 用户线程和守护线程的区别
-  定义不同 - 用户线程:平时使用到的线程均为用户线程。
- 守护线程:用来服务用户线程的线程,例如垃圾回收线程。
 
-  作用区别 - 守护线程和用户线程的区别主要在于Java虚拟机是后存活。
- 用户线程:当任何一个用户线程未结束,Java虚拟机是不会结束的。
- 守护线程:如果只剩守护线程未结束,Java虚拟机结束。
 
2.1 Synchronized
2.1.1 Synchronized关键字
Synchronized是Java中的关键字,是一种同步锁。他修饰的对象有以下几种
- 修饰一个代码块,被修饰的代码块是同步代码块,其作用的范围值大括号括起来的代码,作用的对象是调用这个代码块的对象
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
- 修饰一个静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象
- 修饰一个类,其作用范围是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 更多的功能。
- Lock 与的 Synchronized 区别
- Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内 置特性。Lock 是一个类,通过这个类可以实现同步访问;
- Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户 去手动释放锁,当 synchronized 方法或synchronized 代码块执行完之后, 系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如 果没有主动释放锁,就有可能导致出现死锁现象。
2.2.2 使用Lock实现买票功能
创建多线程的步骤(上)
- 创建资源类,在资源类创建属性和操作方法。
- 创建多个线程,调用资源类的操作方法。
//创建一个资源类 定义相关的属性和方法
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 有以下几点不同:
- Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
- synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在 finally块中释放锁;
- Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断;
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
- Lock 可以提高多个线程进行读操作的效率。 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。
3. 线程间通信及定制化通信
3.1 使用synchronized实现线程之间的通信
- wait和notify方法
- wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
- notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
- 创建多线程的步骤中
创建线程步骤(中部,下部):
- 第一步:创建资源类,在资源类创建属性和操作方法。
- 第二步:在资源类操作方法中进行以下操作
 1. 判断
 2.干活
 3. 通知- 创建多个线程,调用资源类的操作方法
- 实现一个线程+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 虚假唤醒问题:(我称虚假唤醒为偷渡,是不是很形象)
- 在多个生成者和消费者的情况下会出现虚假唤醒问题。
- wait()方法特点:在哪里睡,就在哪里醒。
- 防止刚刚等待,然后被唤醒,然后又抢到锁的情况。
- 倘若在这种情况用if包裹wait()方法,在哪里睡,在那醒,结果就可以逃脱判断筛选(偷渡!!!)
- 其实在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 线程间通信及定制化通信
- Lock接口下有一个实现类,后两个是接口:可重入锁,读锁,写锁。
- Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition()方法。
- 用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 解决方案
- 解决方案-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();
        }
    }
}
- 解决方案-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();
        }
    }
}
- 解决方案-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锁只不过是这几种情况而已,并不是官方定义的。
 总结三个知识点:
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的 Class 对象。
- 对于同步方法块,锁是 Synchonized 括号里配置的对象
5.2 公平锁和非公平锁
看源码秒懂!
- 非公平锁:private Lock lock = new ReentrantLock();当可重入锁的构造函数无参,或者参数为false时,为非公平锁
- 公平锁:相对的为true时,为公平锁。
- 源码:
public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
- 举例理解:
 1)a,b,c3个人卖30张票,非公平锁时就是,一个人把票卖完了;公平锁就是3个人都能卖到票。都有钱赚。
  2)公交车找座位:非公平锁直接坐那。公平锁就是还要礼貌的问候:”这里有人吗?“。。。
-  非公平锁和公平锁的优缺点 
 - 非公平锁:
 -  优点:效率高
-  缺点:容易造成线程饿死
 - 公平锁:
 -  优点:阳光普照,都能吃上饭
-  缺点:效率相对低
 
5.3. 可重入锁
- 可重入锁也称为递归锁。
- synchronized 和 Lock都是可重入锁。前者隐式,后者显式。
- 那到底什么是可重入锁?说实话这两节可我没太听懂,但是这篇文章讲清楚了。
- 什么是可重入锁:什么是 “可重入”,可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
- 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();
    }
}
- 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的区别
- Runnable没有返回值二Callable有返回值
- Runnable不能抛出异常二Runnable可以抛出异常
- Runnable实现的是run方法而Callable实现的是call方法
6.2 Callable使用方式
- 为了实现 Runnable,需要实现不返回任何内容的 run()方法,而对于 Callable,需要实现在完成时返回结果的 call()方法。
- call()方法可以引发异常,而 run()则不能。
- 为实现 Callable 而必须重写 call 方法。
- 不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable。
6.3 FutureTask未来任务类

- 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同学计算位完成,统一汇总
- 代码示例
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 方法 之后的语句。
- CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这 些线程会阻塞
- 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程 不会阻塞)
- 当计数器的值变为 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 读写锁及表锁和行锁

-  表锁:把整张表锁住; 
  行锁:把一行锁住。
-  什么是读写锁? 
  JAVA 的并发包提供了读写锁 ReentrantReadWriteLock, 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称 为排他锁
-  读写锁: 
- 前者共享锁,可能发生死锁;后者独占锁,也可能发生死锁。
- 读可以一起读,写只能一个人写。即别人读的时候不能写,别人写的时候也不能写。这是发生死锁的根本。
- 无论是读还是写,都有可能发生死锁。
- 读锁:读的时候可以进行写的操作,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这篇文章



















