JUC

  • Callable:接口:解决Runnable不方面返回结果的问题
  • ReentrantLock
  • 信号量:Semaphore
  • CountDownLatch:等待所有线程执行完毕后,await返回
  • CopyonwriteArrayList
  • HashTable(不推荐)和ConcurrentHashmap(推荐)

Callable:接口:解决Runnable不方面返回结果的问题

下面代码中Callable是在描述一个任务,且有一个返回值
将Callable对象callable传入FutureTask的构造方法中所创建出来的对象task,就是Callable中描述的任务的维护标识对象.将task传入Thread的构造方法中,等待该线程运行结束后,可以通过task.get()得到任务的返回值.

public class Demo28 {    public static void main(String[] args) {        // 通过 callable 来描述一个这样的任务~~        Callable<Integer> callable = new Callable<Integer>() {            @Override            public Integer call() throws Exception {                int sum = 0;                for (int i = 1; i <= 1000; i++) {                    sum += i;                }                return sum;            }        };        // 为了让线程执行 callable 中的任务, 光使用构造方法还不够, 还需要一个辅助的类.        FutureTask<Integer> task = new FutureTask<>(callable);        // 创建线程, 来完成这里的计算工作~~        Thread t = new Thread(task);        t.start();        // 凭小票来端你自己的麻辣烫.        // 如果线程的任务没有执行完呢, get 就会阻塞.        // 一直阻塞到, 任务完成了, 结果算出来了~~        try {            System.out.println(task.get());        } catch (InterruptedException e) {            e.printStackTrace();        } catch (ExecutionException e) {            e.printStackTrace();        }    }}

ReentrantLock

可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.

lock(): 加锁, 如果获取不到锁就死等.
trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.
unlock(): 解锁

public class Demo29 {    public static void main(String[] args) {        ReentrantLock locker = new ReentrantLock();        // 加锁        locker.lock();        // 抛出异常了. 就容易导致 unlock 执行不到~~        // 解锁        locker.unlock();    }}

把加锁和解锁两个操作分开了,很容易遗漏unlock(容易出现死锁)当多个线程竞争同一个锁的时候就会阻塞…

ReentrantLock 和 synchronized 的区别:
1.synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准
库的一个类, 在 JVM 外实现的(基于 Java 实现).

2.synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活,
但是也容易遗漏 unlock.

3.synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就
放弃.

4.synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启
公平锁模式

信号量:Semaphore

是一个更广义的锁.锁是信号量里第一种特殊情况,叫做”二元信号量”

P操作 acquire申请
V操作 release释放

锁就可以视为”二元信号量”,可用资源就一个,计数器的取值,非О即1

public class Demo30 {    public static void main(String[] args) throws InterruptedException {        // 初始化的值表示可用资源有 4 个.        Semaphore semaphore = new Semaphore(4);        // 申请资源, P 操作        semaphore.acquire();        System.out.println("申请成功");        semaphore.acquire();        System.out.println("申请成功");        semaphore.acquire();        System.out.println("申请成功");        semaphore.acquire();        System.out.println("申请成功");        semaphore.acquire();        System.out.println("申请成功");        // 释放资源, V 操作        // semaphore.release();    }}

CountDownLatch:等待所有线程执行完毕后,await返回

public class Demo31 {    public static void main(String[] args) throws InterruptedException {        // 构造方法的参数表示有几个选手参赛.        CountDownLatch latch = new CountDownLatch(10);                for (int i = 0; i < 10; i++) {            Thread t = new Thread(() -> {                try {                    Thread.sleep(3000);                    System.out.println(Thread.currentThread().getName() + " 到达终点!");                    latch.countDown();                } catch (InterruptedException e) {                    e.printStackTrace();                }            });            t.start();        }        // 裁判就要等待所有的线程到达        // 当这些线程没有执行完的时候, await 就阻塞, 所有的线程都执行完了, await 才返回.        latch.await();        System.out.println("比赛结束!");    }}

CopyonwriteArrayList

写时拷贝,在修改的时候,会创建一份副本出来

这样做的好处,就是修改的同时对于读操作,是没有任何影响的,读的时候优先读旧的版本
不会说出现读到一个”修改了一半”的中间状态,等到修改完毕,再将副本转正.

HashTable(不推荐)和ConcurrentHashmap(推荐)

HashMap本身线程不安全

HashTable对象只有一把锁
如果元素多了,链表就会长,就影响hash表的效率~~就需要扩容(增加数组的长度)
扩容就需要创建一个更大的数组,然后把之前旧的元素都给搬运过去~(非常耗时)

ConcurrentHashmap是针对这个元素所在的链表的头结点来加锁的.

如果你两个线程操作是针对两个不同的链表上的元素,没有线程安全问题,其实不必加锁~~
由于hash 表中,链表的数目非常多,每个链表的长度是相对短的,因此就可以保证锁冲突的概率就非常小了

对于HashTable 来说只要你这次put触发了扩容就一口气搬运完.会导致这次put非常卡顿.
ConcurrentHashMap,每次操作只搬运一点点.通过多次操作完成整个搬运的过程.同时维护一个新的HashMap和一个旧的.查找的时候既需要查旧的也要查新的.插入的时候只插入新的直到搬运完毕再销毁旧的