Java并发包常见类记录

JUC

阻塞队列

阻塞队列(BlockingQueue)通常用于一个线程生产对象,而另一个线程消费这些对象的场景。该队列具有缓冲、消峰限流、解耦生产者与消费者等作用。阻塞队列底层是通过锁机制实现的。队列的4个插入方法:

  1. add:当队列已满,会抛出异常;
  2. offer:插入成功返回true,失败返回false;
  3. offer(obj, 3000, TImeUnit.xxx):阻塞指定时间后仍插入失败则返回false;
  4. put:当队列已满,则阻塞;(常用)

队列的删除方法:

  1. remove:当队列为空时会抛异常;
  2. poll:当队列为空时会返回null;
  3. poll(3000, TimeUnit.xxx):阻塞指定时间后仍然为空则返回null;
  4. take:当队列为空时,会产生阻塞;(常用)

队列的五个实现类的特点:

  1. ArrayBlockingQueue:在创建时指定容量,;
  2. LinkedBlockingQueue:默认大小是Integer,MaxValue,;
  3. PriorityBlockingQueue:优先级队列,可以排序,使用该队列的对象需要实现Comparable接口;
  4. SyschronousQueue:同步队列,容量只能为1;
  5. BlockingDeque:阻塞双端队列,有两套插入和移出方法;

并发映射

常见映射有:

  • HashMap:性能高,但是线程不安全
  • HashTable:性能很低,线程安全,所有方法都使用同步代码块

锁的代价:需要进行上下文切换、和调度延时,等待锁的线程会被挂起直至锁释放,对性能影响很大。

并发哈希映射(ConcurrentHashMap),底层是基于数组+链表,容量默认是16,加载因子默认0.75,扩容默认增加一倍,ConcurrentHashMap在jdk1.8之前为:引入了分段锁机制,底层分了16端,每一段都可以看做是一个HashTable,并发性能更高。1.8之后:使用了CAS无锁算法(Compare and swap):是一种乐观锁技术,没有锁的开销,但是可能会导致失败重试的次数很多,并且将链表替换为红黑树。

CAS:即我认为V的值应该是A,如果是,则将V的值更新为B,否则,不修改,并告诉V的实际值,然后重试;

ConcurrentNavigableMap接口:其实现类是ConcurrentSkipListMap,使用了跳表实现

闭锁

闭锁(CountDownLatch)是一个并发构造,允许一个或者多个线程等待一系列操作完成;使用方法是:给定一个数量初始化,每调用一次countDown方法数量减一,等待线程则通过调用await方法等待这一数量到达零才继续执行。

示例代码:(主线程会等到两个线程的任务都执行完了才会执行)

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch down = new CountDownLatch(2);
        new Thread(new T1("t1", down)).start();
        new Thread(new T1("t2", down)).start();
        down.await();
        System.out.println("主线程执行了!");
    }
    static class T1 implements Runnable{
        private String name;
        private CountDownLatch down;
        T1(String name, CountDownLatch down){
            this.name = name;
            this.down = down;
        }
        @Override
        public void run() {
            System.out.println(name + "线程执行完成!");
            down.countDown();
        }
    }
}

栅栏

栅栏(CyclicBarrier)是一种同步机制,可以试想线程的同步协调,能够对处理一些算法的线程实现同步,也就是说所有线程都需要到达每一点才能继续执行;使用方法:先给定一个数量初始化,每个线程完成一些预处理操作后则执行await方法,每调一次await方法数量减一,当减为零时,所有阻塞方法继续执行。

示例代码:(尽管三个线程预处理时长不同,但最终会等待其他线程执行完成了才继续)

public class CyclicBarrierTest {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyc = new CyclicBarrier(3);
        new Thread(new T1("t1", 3, cyc)).start();
        new Thread(new T1("t2", 1, cyc)).start();
        System.out.println("主线程预处理完成!");
        cyc.await();
        System.out.println("主线程执行完成!");
    }
    static class T1 implements Runnable {
        private CyclicBarrier cyc;
        private int sec;
        private String name;
        T1(String name, int sec, CyclicBarrier cyc) {
            this.name = name;
            this.sec = sec;
            this.cyc = cyc;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(sec * 1000);
                System.out.println(name + "线程预处理完成!");
                cyc.await();
                System.out.println(name + "线程执行完成!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

交换机

交换机(Exchanger)可以交换两个线程的数据,使用方法是直接调用exchange方法,参数为要传递的参数,返回值是对方线程传递的值;示例代码如下:(线程1打印了线程2的消息,线程2打印了线程1的消息)

public class ExchangerTest {
    public static void main(String[] args) {
        Exchanger<String> ex = new Exchanger<>();
        new Thread(new T1("t1", "消息一", ex)).start();
        new Thread(new T1("t2", "消息二", ex)).start();
    }
    static class T1 implements Runnable{
        private String name;
        private String msg;
        private Exchanger<String> ex;
        T1(String name, String msg, Exchanger<String> ex){
            this.name = name;
            this.msg = msg;
            this.ex = ex;
        }
        @Override
        public void run() {
            try {
                String str = ex.exchange(msg);
                System.out.println(name + "线程收到消息:" + str);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程池

也叫执行器服务ExecutorService(实现类为ThreadPoolExecutor)。线程池的初始化参数有核心线程数、最大线程数、存活时间、时间单位、等待队列大小、拒绝服务助手,当提交线程到线程池时,会优先启用核心线程,核心满了则会放到等待队列,等待队列也满了,则启用临时线程,临时线程也达到最大后,则会拒绝服务;除了直接创建线程池,Executors中封装了4中不同参数的线程池如下:

  1. 缓存型线程池(newCachedThreadPool):即大池子小队列,该线程池可以很好的响应客户端请求,适合于高并发的短请求,但如果是长请求,可能会导致线程一致创建而不销毁,进而出现堆溢出,具体特点为:
    • 没有核心线程;
    • 最大线程数为Integer.MAX_VALUE;
    • 存活时间为1分钟;
    • 队列是同步队列。
  2. 固定数量线程池(newFixedThreadPool):即小池子大队列,该线程池可以消峰限流,但是可能不能及时响应请求,具体特点为:
    • 核心线程数等于最大线程数,即没有临时线程,
    • 队列是链表阻塞队列,可以认为无界;
  3. 调度性线程池(newScheduledThreadPool):
    • 固定核心线程数,
    • 最大线程数Integer.MAX_VALUE,
    • 使用无界阻塞延迟队列(DelayedWorkQueue类),并且队列是个优先级队列,当从队列获取元素时候,只有过期元素才会出队列,
    • 可以延时schedule方法)或者周期性scheduleAtFixedRate方法/scheduleWithFixedDelay方法)执行线程。
  4. 单线程线程池(newScheduledThreadPool):单个核心线程,最大线程1,链表阻塞队列。

Callable

是jdk1.5之后提供的新的线程机制,使用call方法执行任务,具有如下特点:

  • 可以自定义返回值类型;
  • 返回值可以接收;
  • 可以抛异常;
  • 只能通过线程池启动。其返回值通过Future的get方法(阻塞方法)接收。

示例代码如下:

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        Future<String> future = service.submit(new T1());
        System.out.println(future.get());
        service.shutdown();
    }
    static class T1 implements Callable<String>{
        @Override
        public String call() throws Exception {
            System.out.println("线程执行了!");
            return "SUCCESS";
        }
    }
}

分叉合并

  1. 是jdk1.7以后出现的;
  2. 将大任务进行拆分成多个小任务分配给不同的线程来执行;
  3. 将拆分的任务的执行结果进行合并;
  4. 这种方式可以提高CPU利用率,在数据量大时可以提高效率;
  5. 分叉合并在分配任务时,会自动平衡任务,若核上原来的任务多则会少分,否则多分;
  6. 分叉合并为了避免慢任务导致的效率降低,采取了work-stealing(工作窃取)策略;即当有核心完成任务后,会随机从其他核心的任务队列尾端窃取任务来执行。
  7. 使用方法是让任务类继承RecursiveTask抽象类,该类的泛型就是结果的类型,如果不需要结果则继承RecursiveAction抽象类;
//分叉合并示例代码,切分累加运算
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinTest {
    public static void main(String[] args) throws InterruptedException {
        ForkJoinPool pool = new ForkJoinPool();
        Sum s = new Sum(0L, 10000000000L, true);
        pool.execute(s);
        Long result = s.join();
        System.out.println(result);
    }
}
class Sum extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    private boolean task;  //任务拆分条件
    Sum(Long start, Long end, boolean split) {
        this.start = start;
        this.end = end;
        this.task = split;
    }
    @Override
    protected Long compute() {
        if (task) {
            Long mid = (end + start) / 2;
            Long mid1 = (mid + start) / 2;
            Long mid2 = (end + mid) / 2;
            Sum s1 = new Sum(start, mid1, false);
            Sum s2 = new Sum(mid1, mid, false);
            Sum s3 = new Sum(mid, mid2, false);
            Sum s4 = new Sum(mid2, end, false);
            s1.fork();
            s2.fork();
            s3.fork();
            s4.fork();
            return s1.join() + s2.join() + s3.join() + s4.join();
        }else{
            Long result = 0L;
            for (Long i = start; i < end; i++) {
                result += i;
            }
            return result;
        }
    }
}

锁(Lock)

功能类似同步代码块(synchronized),但是比同步代码块功能更强大;它支持两种锁机制(公平锁/非公平锁,默认为非公平)。同步代码块只有非公平锁。锁还支持读写锁。

例题

实现ABCD四个线程轮流输出10次

方式一

使用ABCD四把锁分别对应每个线程,每个线程只有获取到自己的锁和下一个线程的锁才开始执行,执行逻辑是先执行业务代码(即输出一次),接着唤醒下一个线程,然后用自己的锁阻塞自己;但这个逻辑需要保证第一次执行的顺序正确,故又添加了闭锁,让第一次执行时,D需要等待C,C需要等待B,B需要等待A;还有在最后一次输出后线程就不需要阻塞了,否则会因为没有唤醒它的线程使得程序不能正常结束,具体代码如下:

import java.util.concurrent.CountDownLatch;
//可以完成每次都顺序输出
public class Main {

    public static void main(String[] args) {
        CountDownLatch a = new CountDownLatch(1);
        CountDownLatch b = new CountDownLatch(1);
        CountDownLatch c = new CountDownLatch(1);
        CountDownLatch d = new CountDownLatch(1);

        Object aa = new Object();
        Object bb = new Object();
        Object cc = new Object();
        Object dd = new Object();

        new Thread(new A(null, a, "A", aa, bb)).start();
        new Thread(new A(a, b, "B", bb, cc)).start();
        new Thread(new A(b, c, "C", cc, dd)).start();
        new Thread(new A(c, null, "D", dd, aa)).start();
    }
}

class A implements Runnable {

    private CountDownLatch self;
    private CountDownLatch pre;
    private String name;
    private final Object curr;
    private final Object next;

    public A(CountDownLatch pre, CountDownLatch self, String name, Object curr, Object next) {
        this.pre = pre;
        this.self = self;
        this.name = name;
        this.curr = curr;
        this.next = next;
    }

    @Override
    public void run() {
        try {
            if (pre != null) {
                pre.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 1; i < 11; i++) {
            synchronized (curr) {
                synchronized (next) {
                    System.out.println(this.name + "-" + i);
                    next.notify();
                }
                try {
                    if(i == 1)
                        if(self != null)
                            self.countDown();
                    if(i != 10)
                        curr.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
方式二

先定义两个全局变量已经输出的次数(0)和总共需要输出的次数(40),接着给每个线程设置0,1,2,3的编号,然后所有线程并发抢锁,抢到锁后先判断已经输出的次数(0)对线程数(4)取余的值是否为当前线程的编号,若是则输出一次并将已经输出的次数加一,然后唤醒所有线程,否则当前线程阻塞并释放锁,由剩下的线程抢锁,直到已经输出的次数等于总共需要输出的次数;具体代码如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//可以保证每一轮都顺序输出
public class Main1 {
    static int total = 10 * 4;
    static int curr = 0;
    static String[] name = {"A", "B", "C", "D"};

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(new T1(0, lock, condition)).start();
        new Thread(new T1(1, lock, condition)).start();
        new Thread(new T1(2, lock, condition)).start();
        new Thread(new T1(3, lock, condition)).start();
    }
}

class T1 implements Runnable {
    private Lock lock;
    private Condition condition;
    private int index;

    public T1(int index, Lock lock, Condition condition) {
        this.index = index;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        while (Main1.curr < Main1.total) {
            lock.lock();
            if (Main1.curr % 4 == index) {
                System.out.print(Main1.name[index]);
                if (Main1.curr % 4 == 3) {
                    System.out.println("-" + (Main1.curr / 4));
                }
                Main1.curr++;
                condition.signalAll();
            } else {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.unlock();
        }
    }
}
方式三

所有线程并发抢锁,抢到锁的线程则执行业务代码(输出一次),接着将本轮的业务代码执行次数(输出次数)加一,然后判断本轮执行的总次数是否等于线程数(4),若是则代表本轮所有线程都执行一次了,故重置本轮执行次数为零,唤醒所有线程;否则代表还有线程本轮没有执行,则阻塞等待;具体代码如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//感觉这种方式效率较高,但是只能保证每一轮所有线程都执行一遍,不能保证每一轮都有序;
public class Main2 {
    static int total = 10;
    static String[] name = {"A", "B", "C", "D"};
    static int state = 0;

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(new T2(0, lock, condition)).start();
        new Thread(new T2(1, lock, condition)).start();
        new Thread(new T2(2, lock, condition)).start();
        new Thread(new T2(3, lock, condition)).start();
    }
}

class T2 implements Runnable {
    private Lock lock;
    private Condition condition;
    private int index;

    public T2(int index, Lock lock, Condition condition) {
        this.index = index;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        for (int i = 0; i < Main2.total; i++) {
            lock.lock();
            System.out.print(Main2.name[index]);
            Main2.state++;
            if (Main2.state == Main2.name.length) {
                Main2.state = 0;
                condition.signalAll();
                System.out.println("-" + i);
            } else {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.unlock();
        }
    }
}
方式四

每次一个线程都不停的判断当前是否该自己输出,若是则加锁输出并将当前值加一,否则继续判断,直到输出总次数满足要求(40);

//
public class Main3 {
    public static void main(String[] args) {
        new Thread(new T3(0)).start();
        new Thread(new T3(1)).start();
        new Thread(new T3(2)).start();
        new Thread(new T3(3)).start();
    }
}
class T3 implements Runnable{
    private volatile static int curr = 0;
    //private static int curr = 0;
    private static final Object obj = new Object();
    private int num;

    public T3(int num) {
        this.num = num;
    }

    @Override
    public void run() {
        while(curr < 40){
            if(curr % 4 == num){
                synchronized (obj){
                    //里面不要这个判断不影响结果
                    //if(curr % 4 == num) {
                        System.out.println(curr + "-" + num);
                        curr++;
                    //}
                }
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值