文章目录
- 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() {@Overridepublic void run() {//调用买票的方法for(int i=0;i<40;i++){ticket.sale();}}},"AA").start();new Thread(new Runnable() {@Overridepublic void run() {//调用买票的方法for(int i=0;i<40;i++){ticket.sale();}}},"BB").start();new Thread(new Runnable() {@Overridepublic void run() {//调用买票的方法for(int i=0;i<40;i++){ticket.sale();}}},"CC").start();}
}
2.2 Lock
2.2.1 什么是Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能。
- 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();//创建三个conditionprivate Condition c1 = lock.newCondition();private Condition c2 = lock.newCondition();private Condition c3 = lock.newCondition();//打印五次 参数第几轮public void print5(int loop) throws InterruptedException {//上锁lock.lock();try {//判断while (flag != 1){//等待c1.await();}//干活for(int i = 1;i <= 5;i++ ){System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);}//修改标志位flag = 2;//通知c2.signal();}finally {//释放锁lock.unlock();}}//打印10次public void print10(int loop) throws InterruptedException {lock.lock();try {//判断while ( flag != 2){c2.await();//等待}for(int i=0;i<=10;i++){System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);}flag = 3;c3.signal();}finally {lock.unlock();}}public void print15(int loop) throws InterruptedException {lock.lock();try {while ( flag != 3 ){c3.await();}for(int i=0;i<=15;i++){System.out.println(Thread.currentThread().getName()+"::"+i+"轮数"+loop);}flag = 1;c1.signal();}finally {lock.unlock();}}}public class ThreadDemo3 {public static void main(String[] args) {System.out.println("开始运行代码");ShareResource shareResource = new ShareResource();new Thread(()->{for(int i=0;i<10;i++){try {shareResource.print5(i);} catch (InterruptedException e) {e.printStackTrace();}}},"AA").start();new Thread(()->{for(int i=0;i<10;i++){try {shareResource.print10(i);} catch (InterruptedException e) {e.printStackTrace();}}},"BB").start();new Thread(()->{for(int i=0;i<10;i++){try {shareResource.print15(i);} catch (InterruptedException e) {e.printStackTrace();}}},"CC").start();}
}
4.集合的线程安全
4.1 ArrayList集合线程不安全演示
public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合ArrayList<String> list = new ArrayList<>();for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}
4.2 解决方案
- 解决方案-Vector(jdk1.0方案,太老了)
在接口List下除了ArrayList实现类外,还有一个Vector实现类。
public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合// ArrayList<String> list = new ArrayList<>();Vector<String> list = new Vector<>();for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}
- 解决方案-Collections(这种方式也很老,这两种方案用的都不多,用的最多的是JUC里的解决方案)
借助工具类Collections下的静方法synchronizedList(List list)
public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合// ArrayList<String> list = new ArrayList<>();//创建vector解决
// Vector<String> list = new Vector<>();
// Collections解决List<String> list = Collections.synchronizedList(new ArrayList<String>());for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}
- 解决方案-CopyOnWriteArrayList
这个类也被称为“写时复制技术”
public class ThreadDemo4 {public static void main(String[] args) {//创建list 集合// ArrayList<String> list = new ArrayList<>();//创建vector解决
// Vector<String> list = new Vector<>();
// Collections解决
// List<String> list = Collections.synchronizedList(new ArrayList<String>());CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();for(int i=0;i<10;i++){new Thread(new Runnable() {@Overridepublic void run() {//集合中添加内容list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}},String.valueOf(i)).start();}}
}
源码分析
public boolean add(E e) {//可重入锁final ReentrantLock lock = this.lock;//上锁lock.lock();try {//1.得到内容Object[] elements = getArray();//2.得到旧数组长度int len = elements.length;//拷贝到一个新数组,以扩展一个元素的形式拷贝到新数组Object[] newElements = Arrays.copyOf(elements, len + 1);//将内容写到新数组,len是旧数组的长度,他他作为下标是应该是扩展一位的意思。newElements[len] = e;//新数组覆盖之前的旧数组setArray(newElements);return true;} finally {//解锁lock.unlock();}
}
//妙啊!!!
4.3 HashSet线程不安全
类似上面操作就行,解决方案 CopyOnWriteArraySet
public class ThreadDemo5 {public static void main(String[] args) {
// HashSet<String> set = new HashSet<>();CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();for(int i=0;i<10;i++){new Thread(()->{set.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(set);},String.valueOf(i)).start();}}
}
4.4. HashMap线程不安全
解决方案 ConcurrentHashMap
//演示HashMap
// Map<String,String> map = new HashMap<>();Map<String,String> map = new ConcurrentHashMap<>();for (int i = 0; i <30; i++) {String key = String.valueOf(i);new Thread(()->{//向集合添加内容map.put(key,UUID.randomUUID().toString().substring(0,8));//从集合获取内容System.out.println(map);},String.valueOf(i)).start();
5. 多线程锁
5.1 演示锁的八种情况
演示代码
package com.atguigu.sync;import java.util.concurrent.TimeUnit;class Phone {public static synchronized void sendSMS() throws Exception {//停留4秒TimeUnit.SECONDS.sleep(4);System.out.println("------sendSMS");}public synchronized void sendEmail() throws Exception {System.out.println("------sendEmail");}public void getHello() {System.out.println("------getHello");}
}/*** @Description: 8锁*
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS*/public class Lock_8 {public static void main(String[] args) throws Exception {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(() -> {try {phone.sendSMS();} catch (Exception e) {e.printStackTrace();}}, "AA").start();Thread.sleep(100);new Thread(() -> {try {// phone.sendEmail();// phone.getHello();phone2.sendEmail();} catch (Exception e) {e.printStackTrace();}}, "BB").start();}
}
8锁只不过是这几种情况而已,并不是官方定义的。
总结三个知识点:
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的 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();// synchronizedObject o = new Object();new Thread(()->{synchronized(o) {System.out.println(Thread.currentThread().getName()+" 外层");synchronized (o) {System.out.println(Thread.currentThread().getName()+" 中层");synchronized (o) {System.out.println(Thread.currentThread().getName()+" 内层");}}}},"t1").start();}
}
- 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 {@Overridepublic void run() {}
}class MyClass2 implements Callable{@Overridepublic Integer call() throws Exception {return 200;}
}public class Demo1 {public static void main(String[] args) throws ExecutionException, InterruptedException {//Runnable创建线程
// new Thread(new MyClass1(),"AA").start();FutureTask futureTask1 = new FutureTask<>(new MyClass2());//lamd表达式FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {System.out.println(Thread.currentThread().getName()+"come in callable");return 1024;});//创建一个线程new Thread(futureTask2,"AA").start();new Thread(futureTask1,"BB").start();while (!futureTask1.isDone()){System.out.println("wait....");}while (!futureTask2.isDone()){System.out.println("wait....");}//调用FutureTask的get方法System.out.println("futureTask1:"+futureTask2.get());System.out.println("futureTask2:"+futureTask1.get());System.out.println(Thread.currentThread().getName()+"over...");}
}
7. JUC强大的辅助类
7.1. 减少计数CountDownLatch
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行 减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法 之后的语句。
- 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) {//创建CyclicBarrierCyclicBarrier cyclicBarrier =new CyclicBarrier(NUMBER,()->{System.out.println("*****集齐7颗龙珠就可以召唤神龙");});//集齐七颗龙珠过程for (int i = 1; i <=7; i++) {new Thread(()->{try {System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");//等待cyclicBarrier.await();} catch (Exception e) {e.printStackTrace();}},String.valueOf(i)).start();}}
}
7.3. 信号灯Semaphore
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线 程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方 法获得许可证,release 方法释放许可。
场景: 抢车位, 6 部汽车 3 个停车位
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;public class SemaphoreDemo {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);for(int i=0;i<=6;i++){new Thread(()->{//抢占try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+"抢到车位");//设置停车时间TimeUnit.SECONDS.sleep(new Random().nextInt(5));System.out.println(Thread.currentThread().getName()+"--------离开了车位");} catch (InterruptedException e) {e.printStackTrace();}finally {semaphore.release();}},String.valueOf(i)).start();}}
}
8. ReentrantReadWriteLock读写锁
1. 乐观锁和悲观锁
8.2 读写锁及表锁和行锁
-
表锁:把整张表锁住;
行锁:把一行锁住。 -
什么是读写锁?
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这篇文章