JUC(五)Callable
Callable接口
创建线程的几种方式
- 继承Thread类
- 实现Runnable接口
- 通过Callable接口
- 线程池
使用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的更多相关文章
- JUC之Callable接口回顾和JUC辅助类
		Callable接口和JUC辅助类 Callable接口: 回顾: 创建线程的四种方式: 继承Thread 实现runnable接口 实现callable接口 使用线程池 之前的文章:多线程编程1-定 ... 
- 基于接口回调详解JUC中Callable和FutureTask实现原理
		Callable接口和FutureTask实现类,是JUC(Java Util Concurrent)包中很重要的两个技术实现,它们使获取多线程运行结果成为可能.它们底层的实现,就是基于接口回调技术. ... 
- JUC 一 Callable
		java.util.concurrent.Callable是一个泛型接口,只有一个call()方法 Callable和Runnable的区别 Callable使用call()方法,Runnable使用 ... 
- Java并发编程之线程创建和启动(Thread、Runnable、Callable和Future)
		这一系列的文章暂不涉及Java多线程开发中的底层原理以及JMM.JVM部分的解析(将另文总结),主要关注实际编码中Java并发编程的核心知识点和应知应会部分. 说在前面,Java并发编程的实质,是线程 ... 
- JUC之文章整理以及汇总
		JUC文章汇总 JUC部分将学习<JUC并发编程的艺术>和<尚硅谷-大厂必备技术之JUC并发编程>进行博客的整理,各文章中也会不断的完善和丰富. JUC概述 JUC的视频学习和 ... 
- Java多线程系列--“JUC线程池”06之 Callable和Future
		概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ... 
- Java - "JUC线程池" Callable与Future
		Java多线程系列--“JUC线程池”06之 Callable和Future Callable 和 Future 简介 Callable 和 Future 是比较有趣的一对组合.当我们需要获取线程的执 ... 
- Java多线程(十五):CountDownLatch,Semaphore,Exchanger,CyclicBarrier,Callable和Future
		CountDownLatch CountDownLatch用来使一个线程或多个线程等待到其他线程完成.CountDownLatch有个初始值count,await方法会阻塞线程,直到通过countDo ... 
- JUC学习笔记(五)
		JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html JUC学习笔记(二)https://www.cnblogs.com/lm66/p/1511 ... 
- java多线程系类:JUC线程池:06之Callable和Future(转)
		概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ... 
随机推荐
- 字符过滤流 对象流---->ObjectInputStream : 用法
			1创建字输入节点流FileInputStram fis = new FileInputStream("读入的文件的路径");2创建对象输入过滤流 包装字节流ObjectInputS ... 
- 游戏内存优化之使用16位纹理/NPOT
			转自:https://blog.csdn.net/oqqQuZi1234567/article/details/41749599 图片文件大小和纹理内存占用是两码事.假设他们是帐篷.图片文件就相当于帐 ... 
- Windows使用技巧(持续更新)
			如何将应用添加到鼠标右键菜单? 1. Win+R 输入:regedit打开注册表 2. 找到HKEY_CLASSES_ROOT\Directory\Background\shell,在该路径下创建项 ... 
- decode procedure
			1 test data preparation 1> select representative data voice to match real application scenario ... 
- 4.Vue组件
			一.组件化开发概述 1.组件化开发思想 标准 分治:不同的功能分配到不同的组件中 重用: 组合 2.编程中的组件化思想体现 3.组件化规范: Web Components 我们希望尽可能多的重用代码 ... 
- 【js】js执行机制-js单线程-同步和异步
			js是单线程 即同一个时间只能做一件事,JavaScript是为处理页面中用户的交互,以及操作DOM而诞生的.比如我们对某个DOM元素进行添加和删除操作,不能同时进行.应该先进行添加,之后在进行删除. ... 
- Spring AOP的动态代理原理和XML与注解配置
			AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强. 相关术语: Target(目标对象):代理的目标对象 Pr ... 
- MariaDB简介
			一.什么是数据库 DB 与 DBMS :DB(DataBase)即数据库,存储已经组织好的数据的容器.DBMS(DataBase Manage System)是数据库管理系统用来对数据库及数据库中的数 ... 
- git的回退以及合并,删除什么的
			有时候不小心合并了别的分支中的commit.我们需要回退某些提交记录.可以通过reset来操作,reset 会回退到指定commit.这种方式会删除记录,我们最好使用revert命令来操作 git r ... 
- loadrunner获取时间戳
			web_save_timestamp_param("tStamp", LAST); //取时间戳 
