JUC并发编程总结(一)

点我跳转至JUC并发编程总结(二)

JUC并发编程总结(一)

  • 基础部分内容
    • 线程和进程
    • synchronized和Lock
      • 1. 区别
      • 2. 生产者消费者问题
      • 3. 相关面试题
      • 4. 小结
    • 集合类线程不安全
      • 解决方案
  • 线程池和辅助类相关内容
    • 线程类接口
      • callable
    • 常用辅助类及读写锁、队列
      • 1. CountDownLatch
      • 2. CyclicBarrier
      • 3. Semaphore
      • 4. 读写锁
      • 5. 队列
    • 线程池
      • 工具类,三大方法。
      • 七大参数
      • 示例
      • 线程设置
  • Reference:

基础部分内容

线程和进程

  • CPU密集型和IO密集型
  • 获取cpu核心数
    System.out.println(Runtime.getRuntime().availableProcessors());
    
  • 线程的几个状态,打开Thread类看源码
    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,//等待
        TIMED_WAITING,//等待超时
        TERMINATED;//终止
    }
    
  • sleep和wait区别 TimeUnit.SECONDS.sleep(5);
  1. 来自不同类 thread/Object
  2. 锁释放 不释放/释放
  3. 适用范围 任何地方/同步代码块
  4. 捕获异常 需要/不需要

synchronized和Lock

1. 区别

  1. synchronized是关键字,Lock是类
  2. synchronized无法判断锁状态,Lock可以判断是否获取到了锁
  3. synchronized会自动释放,Lock必须手动释放不然会死锁。
  4. synchronized会阻塞会一直等待,Lock不一定一直等待
  5. synchronized可重入锁非公平;Lock,可重入锁,可以判断锁,非公平(可以设置)
  6. synchronized适合少量代码同步问题,lock适合大量。
  7. JUC并发编程总结(一)_第1张图片

2. 生产者消费者问题

  • synchronized版,防止虚假唤醒问题。判断有if替换为while
class Data {
    private int num = 0;
    public synchronized void increment() throws InterruptedException {
        while (num != 0) {  //防止虚假唤醒问题
            this.wait();
        }
        num++;
        System.out.println("生产了一个:" + num);
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        while (num == 0) {
            this.wait();
        }
        num--;
        System.out.println("消费了一个:" + num);
        this.notifyAll();
    }
    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());
        Data data = new Data();
        new Thread(() -> {
            data.increment();
        }).start();

        new Thread(() -> {
            data.decrement();
        }).start();
    }
}
  • lock版生产者消费者问题
class Data2 {
    private int num = 0;
    Lock lock =new ReentrantLock();
    Condition condition= lock.newCondition();
    public  void increment()   {
        try {
        lock.lock();			//业务代码写在try里,这是模板
        while (num != 0) {  //防止虚假唤醒问题
            condition.await();
        }
        num++;
        System.out.println("生产了一个:" + num);
        condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public  void decrement()   {
        lock.lock();
        try {
        while (num == 0) {
                condition.await();
        }
        num--;
        System.out.println("消费了一个:" + num);
        condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
 }

3. 相关面试题

  • 两个线程同时给数组里写0到100。
public class Demo {
    public static void add(List<Integer> a,Integer b){
        a.add(b);
    }
    public static void main(String[] args) throws InterruptedException {
        List<Integer> a=new LinkedList<>();
        ReentrantLock lock=new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        AtomicInteger num= new AtomicInteger(1);

        Thread t1=  new Thread(()->{
            for (int i=0;i<100;i++){
                lock.lock();
                try {
                while (num.get() !=1){
                        condition1.await();
                }
                if (i%2==0){
                    add(a,i);
                    num.set(2);
                }
                    condition2.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        });
      Thread t2=  new Thread(()->{
            for (int i=0;i<100;i++){
                lock.lock();
                try {
                while (num.get()!=2){
                        condition2.await();
                }
                if (i%2!=0){
                    add(a,i);
                    num.set(1);
                }
                condition1.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        });
      t1.start();
      t2.start();
      t1.join();
      t2.join();
      System.out.println(a);
    }
}

4. 小结

  1. condition,精准唤醒。如上线程加减问题。
  2. 8锁现象,锁的是谁?
    • synchronized锁的对象是方法调用者。

集合类线程不安全

  • 普通集合类用多线程操作会出现并发修改异常。ConcurrentModificationException

解决方案

List<String> list =new Vector<>();
List<String> list2 = Collections.synchronizedList(new ArrayList<>());
List<String> list3 =new CopyOnWriteArrayList<>();

Set<String> set =Collections.synchronizedSet(new HashSet<>());
Set<String> set2 =new  CopyOnWriteArraySet<>();

ConcurrentHashMap<String,String> map=new ConcurrentHashMap<>(); 

线程池和辅助类相关内容

线程类接口

callable

JUC并发编程总结(一)_第2张图片

  • 示例:
    public class Demo {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            MyThread thread =new MyThread();
            FutureTask ft=new FutureTask(thread);
            new Thread(ft,"A00").start();
            String result= (String) ft.get();
            
            FutureTask ft2=new FutureTask(new MyThread());
            new Thread(ft2,"A01").start();
            String result2= (String) ft.get();
        }
    }
    class  MyThread implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "返回泛型接口的String类";
        }
    }
    

常用辅助类及读写锁、队列

1. CountDownLatch

  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
  • 示例:
public class Demo03 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch cdl=new CountDownLatch(5); //倒计时为5
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"线程执行完成");
                cdl.countDown();
            }).start();
        }
        cdl.await();
        System.out.println("exit");
    }
}

2. CyclicBarrier

  • 大概的意思就是一个可循环利用的屏障。它的作用就是会让所有线程都等待完成后才会继续下一步行动。
  • 示例:
        CyclicBarrier cb=new CyclicBarrier(5,()->{
            System.out.println("五个县城全部执行完成!"); 
            //会执行2次,可以设置执行定期任务。
        });
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"线程执行完成");
                cb.await(); //try..catch
            }).start();
        }

3. Semaphore

  • Semaphore 是一个计数信号量,必须由获取它的线程释放。常用于限制可以访问某些资源的线程数量,例如通过 Semaphore 限流。Semaphore 只有3个操作:初始化、增加、减少。
  • 示例:
public class StudySemaphore {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        
        //信号量,只允许 3个线程同时访问
        Semaphore semaphore = new Semaphore(3);

        for (int i=0;i<10;i++){
            final long num = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取许可
                        semaphore.acquire();
                        //执行
                        System.out.println("Accessing: " + num);
                        Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
                        //释放
                        semaphore.release();
                        System.out.println("Release..." + num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        executorService.shutdown();
    }

}

4. 读写锁

  • ReadWriteLockLock一样也是一个接口,提供了readLockwriteLock两种锁的操作机制,一个是只读的锁,一个是写锁。
  • 读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的(排他的)。每次只能有一个写线程,但是可以有多个线程并发地读数据。
  • 所有读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。
  • 互斥原则:
  1. 读-读能共存 (但不能写)
  2. 读-写不能共存 (脏读、不可重复读、幻读)
  3. 写-写不能共存 (显然不能…)

5. 队列

JUC并发编程总结(一)_第3张图片

  • 队列关系模型。
    JUC并发编程总结(一)_第4张图片
  • 对列四组api:1.抛异常。2.不抛异常。3.阻塞等待。4.超时等待
    JUC并发编程总结(一)_第5张图片
  • 用同步队列实现生产者消费者模型。
         SynchronousQueue<String> blockingDeque= new SynchronousQueue<String>();
        new Thread(()->{
            blockingDeque.put("1");  //try...catch
            blockingDeque.put("2");
            blockingDeque.put("3");
        },"生产者").start();
        new Thread(()->{
            System.out.println("消费了:"+blockingDeque.take());
            System.out.println("消费了:"+blockingDeque.take());
            System.out.println("消费了:"+blockingDeque.take());
        },"消费者").start();

线程池

JUC并发编程总结(一)_第6张图片

工具类,三大方法。

        ExecutorService tp1= Executors.newSingleThreadExecutor();       //单个线程
        ExecutorService tp2= Executors.newFixedThreadPool(5);  //创建线程池大小
        ExecutorService tp3= Executors.newCachedThreadPool();               //遇强则强,遇弱则弱

        for (int i = 0; i <50 ; i++) {
            tp1.execute(()->{
                System.out.println(Thread.currentThread().getName()+"ok");
            });
        }

七大参数

  • 我们打开ThreadPoolExecutor构造方法,可以看到这七大参数。
        public ThreadPoolExecutor(int corePoolSize,		//核心线程数
                                  int maximumPoolSize,	//最大线程数
                                  long keepAliveTime,	//空闲等待时间
                                  TimeUnit unit,		//空闲等待时间单位
                                  BlockingQueue<Runnable> workQueue,	//工作队列
                                  ThreadFactory threadFactory,		//使用的工程,
                                  RejectedExecutionHandler handler	//队列满了的时候拒绝策略。
                                  ) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.acc = System.getSecurityManager() == null ?
                    null :
                    AccessController.getContext();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
    
  • 关于拒绝策略,提供了以下几个。
    new ThreadPoolExecutor.AbortPolicy();           //队列满了,抛出异常
    new ThreadPoolExecutor.CallerRunsPolicy();      //哪来回哪去,一般丢给主线程
    new ThreadPoolExecutor.DiscardOldestPolicy();   //丢列满了丢掉任务,不抛出异常
    new ThreadPoolExecutor.DiscardPolicy();         //队列满了,尝试和最早的竞争资源
    

示例

ExecutorService pool=new ThreadPoolExecutor(3,
        5,
        3, TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i <50 ; i++) {
    int finalI = i;
    pool.execute(new Thread(()->{
        System.out.println(Thread.currentThread().getName()+"------"+ finalI);
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }));
}

线程设置

  • cpu密集型:几核就是几,保证占用率最高。
  • io密集型:应该大于你程序中十分消耗io的线程。例如下载任务。

Reference:

  1. https://www.jianshu.com/p/333fd8faa56e
  2. https://www.jianshu.com/p/e233bb37d2e6
  3. https://www.jianshu.com/p/ec637f835e08
  4. https://blog.csdn.net/j080624/article/details/82790372
  5. https://blog.csdn.net/youanyyou/article/details/78990156

你可能感兴趣的:(java)