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