Callable接口

创建线程的几种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 通过Callable接口
  4. 线程池

使用Runnable接口无法获取到线程返回的结果,因此在jdk1.5后java提供了Callable接口。

Callable接口的特点

  • 需要实现带返回结果的call方法
  • 无法计算返回结果则会抛出异常

FutureTask

实现Thread没有Callable构造的问题

由于Thread没有Callable构造的问题,所以callable不能像runnable一样直接创建一个线程,这时候需要通过一个中间类--FutureTask来实现,可以看到FutureTask既实现了Runnable也包含callable:

public class FutureTask<V> implements RunnableFuture<V> {
private Callable<V> callable;
...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
...
}
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
return 1024;
}
}
public class CallableTest { public static void main(String[] args) {
//var integerFutureTask = new FutureTask<Integer>(new MyCallable());
var integerFutureTask = new FutureTask<>(() -> {
return 1024;
});
new Thread(integerFutureTask).start();
}
}
FutureTask原理
  • 为任务单独开启一个线程
  • 线程执行FutureTask只会执行一次,第二次会直接返回一个结果
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
return 200;
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//var integerFutureTask = new FutureTask<Integer>(new MyCallable());
var integerFutureTask = new FutureTask<>(() -> {
return 1024;
});
new Thread(integerFutureTask).start();
while(!integerFutureTask.isDone()) {
System.out.println("wait..");
}
System.out.println(integerFutureTask.get());
new Thread(integerFutureTask).start();
while(!integerFutureTask.isDone()) {
System.out.println("wait..");
}
System.out.println(integerFutureTask.get());
}
}

wait..

wait..

wait..

wait..

wait..

wait..

wait..

1024

1024

相同FutureTask的两个线程第二个线程执行直接返回了结果

JUC辅助类

CountDownLatch计数器

类似os的整型信号量,countDown方法对计数-1,await判断计数小于零则堵塞当前线程。

public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
var count = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
int x = i;
new Thread(() -> {
System.out.println(x + " left.");
count.countDown();
}, String.valueOf(i)).start();
}
count.await();
System.out.println("Over.");
}
}

CyclicBarrier循环栅栏

  • CyclicBarrier为循环阻塞,当阻塞个数达到设置数量则会触发相应事件
  • 构造器用于设置阻塞数量和事件
  • await方法增加阻塞个数
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("CyclicBarrier event happen.");
});
for (int i = 0; i < 7; i++) {
int x = i;
new Thread(() -> {
System.out.println(x);
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}

Semaphore 信号量

  • acquire:消耗资源
  • release:释放资源
public static void main(String[] args) {
Semaphore s = new Semaphore(3); for (int i = 0; i < 6; i++) {
int x = i;
new Thread(() -> {
try {
s.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(x + "comes in.");
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(x + "leaves.");
s.release();
}).start();
}
}

读写锁

乐观锁和悲观锁

乐观锁和悲观锁都是并发控制的方式

  • 悲观锁是指在操作数据之前,先获取锁,确保自己是独占访问数据的,其他人必须等待锁的释放才能访问。它适用于数据冲突概率比较高的场景,如写操作比较频繁的场景。常见的悲观锁实现包括数据库中的行锁、表锁等。
  • 乐观锁是指不加锁,而是在每次操作数据时先读取数据的版本号,然后提交数据更新时比较版本号,如果版本号相同,则说明期间没有其他人修改过数据,可以直接更新,否则需要进行冲突处理。乐观锁适用于数据冲突概率比较低的场景,如读操作比较频繁的场景。常见的乐观锁实现包括数据库中的CAS(Compare And Swap)操作、版本号机制等。

总的来说,悲观锁是保守的并发控制方式,能够确保数据的一致性,但会导致系统的性能下降;而乐观锁是一种乐观的并发控制方式,性能较高,但需要处理冲突。在实际场景中,应根据具体情况选择合适的并发控制方式。

表锁和行锁

  • 表锁是对整张表进行加锁即一个事务在对表进行读或写操作时会锁定整张表,其他事务只能等待锁的释放才能访问该表。表锁通常应用于数据操作比较少或数据操作比较大的情况,如数据导入、备份和复制等操作,以减少锁的竞争。

  • 行锁是对表中的一行或多行进行加锁即一个事务在对表中的某行或某几行进行读或写操作时会锁定这些行,其他事务只能等待锁的释放才能访问这些行。行锁通常应用于数据操作比较频繁、并发访问比较高的情况,如在线事务处理系统,以保证数据的一致性。

  • 行锁会发生死锁而表锁不会。

读锁与写锁

  • 读锁又称为共享锁,允许多个线程进行读操作;写锁又称为独占锁,线程在进行写操作的时候不允许其他线程进行写操作
  • 读锁也能够造成死锁:比如两个线程都在进行读写操作,线程一写操作要在线程二读之后,线程二写操作又要在线程一读之后
  • 写锁造成死锁:两个线程同时对两条记录进行写操作

模拟读写:

class MyCache {
private volatile HashMap<Integer, Integer> map = new HashMap<>();
public void set(Integer key, Integer val) {
System.out.println(Thread.currentThread().getName() + " is setting " + key);
map.put(key, val);
System.out.println(Thread.currentThread().getName() + " is setting over " + val);
}
public Integer get(Integer key) {
System.out.println(Thread.currentThread().getName() + " is getting " + key);
var result = map.get(key);
System.out.println(Thread.currentThread().getName() + " is getting over " + key);
return map.get(result);
}
} public class ReadWriteLockDemo {
public static void main(String[] args) {
var cache = new MyCache();
for (int i = 0; i < 5; i++) {
final var num = i;
new Thread(() -> {
cache.set(num, num);
}, String.valueOf(num)).start();
} for (int i = 0; i < 5; i++) {
final var num = i;
new Thread(() -> {
cache.get(num);
}, String.valueOf(num)).start();
}
}
}

结果出现写操作没有结束但是读的情况

0 is setting 0
3 is getting over 3
4 is setting 4
ReentrantReadWriteLock
  • ReentrantReadWriteLock.writelock()设置写锁
  • ReentrantReadWriteLock.readlock()设置读锁
class MyCache {
private final HashMap<Integer, Integer> map = new HashMap<>();
private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
public void set(Integer key, Integer val) {
try {
rwlock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " is setting " + key);
map.put(key, val);
System.out.println(Thread.currentThread().getName() + " is setting over " + val);
} finally {
rwlock.writeLock().unlock();
}
}
public Integer get(Integer key) {
try {
rwlock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " is getting " + key);
var result = map.get(key);
System.out.println(Thread.currentThread().getName() + " is getting over " + key);
return map.get(result);
} finally {
rwlock.readLock().unlock();
}
}
} public class ReadWriteLockDemo {
public static void main(String[] args) {
var cache = new MyCache();
for (int i = 0; i < 5; i++) {
final var num = i;
new Thread(() -> {
cache.set(num, num);
}, String.valueOf(num)).start();
} for (int i = 0; i < 5; i++) {
final var num = i;
new Thread(() -> {
cache.get(num);
}, String.valueOf(num)).start();
}
}
}
写锁的降级

上面的读写锁中存在这一个缺陷:

  • 由于读锁是共享锁,所以不断有读操作执行的话,写线程就会饥饿

  • 写操作的时候,是能够执行读操作的

    public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); // 锁降级
writeLock.lock();
System.out.println("get writeLock."); readLock.lock();
System.out.println("get readLock."); writeLock.unlock();
readLock.unlock();
}

对写锁降级为读锁:在写操作的时候就上读锁,防止其他写操作上写锁,这样就保证了写操作之后能立刻被读

读锁不能升级为写锁
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); readLock.lock();
System.out.println("get readLock."); writeLock.lock();
System.out.println("get writeLock."); writeLock.unlock();
readLock.unlock();
}

会因无法获取到写锁而使主线程一直处于堵塞队列

JUC(五)Callable的更多相关文章

  1. JUC之Callable接口回顾和JUC辅助类

    Callable接口和JUC辅助类 Callable接口: 回顾: 创建线程的四种方式: 继承Thread 实现runnable接口 实现callable接口 使用线程池 之前的文章:多线程编程1-定 ...

  2. 基于接口回调详解JUC中Callable和FutureTask实现原理

    Callable接口和FutureTask实现类,是JUC(Java Util Concurrent)包中很重要的两个技术实现,它们使获取多线程运行结果成为可能.它们底层的实现,就是基于接口回调技术. ...

  3. JUC 一 Callable

    java.util.concurrent.Callable是一个泛型接口,只有一个call()方法 Callable和Runnable的区别 Callable使用call()方法,Runnable使用 ...

  4. Java并发编程之线程创建和启动(Thread、Runnable、Callable和Future)

    这一系列的文章暂不涉及Java多线程开发中的底层原理以及JMM.JVM部分的解析(将另文总结),主要关注实际编码中Java并发编程的核心知识点和应知应会部分. 说在前面,Java并发编程的实质,是线程 ...

  5. JUC之文章整理以及汇总

    JUC文章汇总 JUC部分将学习<JUC并发编程的艺术>和<尚硅谷-大厂必备技术之JUC并发编程>进行博客的整理,各文章中也会不断的完善和丰富. JUC概述 JUC的视频学习和 ...

  6. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  7. Java - "JUC线程池" Callable与Future

    Java多线程系列--“JUC线程池”06之 Callable和Future Callable 和 Future 简介 Callable 和 Future 是比较有趣的一对组合.当我们需要获取线程的执 ...

  8. Java多线程(十五):CountDownLatch,Semaphore,Exchanger,CyclicBarrier,Callable和Future

    CountDownLatch CountDownLatch用来使一个线程或多个线程等待到其他线程完成.CountDownLatch有个初始值count,await方法会阻塞线程,直到通过countDo ...

  9. JUC学习笔记(五)

    JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html JUC学习笔记(二)https://www.cnblogs.com/lm66/p/1511 ...

  10. java多线程系类:JUC线程池:06之Callable和Future(转)

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

随机推荐

  1. K8SPod进阶资源限制以及探针

    一.Pod 进阶 1.资源限制 当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量. 最常见的可设定资源是 CPU 和内存大小,以及其他类型的资源. 当为 Pod 中的容器指定了 reque ...

  2. K8S中Pod概念

    一.资源限制 Pod 是 kubernetes 中最小的资源管理组件,Pod 也是最小化运行容器化应用的资源对象.一个 Pod 代表着集群中运行的一个进程.kubernetes 中其他大多数组件都是围 ...

  3. IT工具知识-12:RTL8832AU网卡在WIN10更新KB5015807后出现无法正常连接的一种解决方法

    系统配置 硬件配置 使用网卡为Fenvi的FU-AX1800 USB外置网卡(官网驱动同AX1800P) 问题描述 在win10自动更新了KB5015807出现了wifi开机无法自动连接,wifi图标 ...

  4. python3安装turtle失败问题

    失败截图: 解决办法: 1.下载turtle-0.0.2 2.解压文件夹到指定目录 3.打开setup.py找到第四十行修改 4.打开cmd进入turtle-0.0.2所在文件夹的上一层文件夹,执行p ...

  5. 拉勾java核心类库--String类

    String类 @author :magiclx 摘要:String类是用final进行修饰的,关于字符串的一个类,其中包含很多个方法 关键词:String,常量池,正则表达式,数组转化 正文: 参考 ...

  6. 并发QPS公式估算

    一.经典公式1: 一般来说,利用以下经验公式进行估算系统的平均并发用户数和峰值数据 1)平均并发用户数为 C = nL/T 2)并发用户数峰值 C' = C + 3*根号C C是平均并发用户数,n是l ...

  7. linux命令补充

    1.nohup nohup /usr/local/node/bin/node /www/im/chat.js >> /usr/local/node/output.log 2>& ...

  8. 使用selemium被反爬解决方法

    使用selenium进行自动化的时候,如csdn登录时可能会遇到检测反爬,从而需要验证       1. 反爬 有时候,我们利用 Selenium 自动化爬取某些网站时,极有可能会遭遇反爬. 实际上, ...

  9. Python使用Eel和HTML开发桌面应用GUI直接用web前端的VUE+VANT来做

    python的gui太难用了,唯一能配置独立前端的程序只有web.所以用web做前端,到python,完美! 环境准备    Python 3.9    Chrome浏览器(由于Eel是直接调用的Ch ...

  10. eclipse console 控制台输出乱码解决办法

    一.console输出日志显示乱码 二.在类编辑处点击右键 Run As --> Run Configurations 三.在Common中设置字符集 gbk 四.restart 搜索 复制