JUC并发编程

1、什么是JUC

java.util

java.util.concurrent

java.util.concurrent.atomic

java.util.concurrent.locks

JUC就是java的工具包

业务:普通的线程代码 Thread

Runnable 没有返回值,效率相比Callable更低

2、线程和进程

进程:应用程序:如QQ

一个进程往往可以包括多个线程,至少包括一个

Java默认有两个线程:主线程,守护线程(GC)

java不能开启线程,通过本地方法进行调用C++的方法

并发和并行

并发(多线程操作同一个资源)

  • CPU一核,模拟多条线程,天下武功,唯快不破,快速交替

并行(多个人一起行走)

  • CPU多核,多条线程同时执行;线程池

并发编程的本质:充分利用CPU的资源

线程的状态

新生状态:new

运行状态:blocket

等待:watting

超时等待:time_watting

终止:terminated

wait/sleep的区别

1、来自不同的类

wait=>Object

sleep=>Thread

企业当中一般用TimeUnit

2、关于锁的释放

wait,会释放锁

sleep,不会释放锁

3、使用的范围不同

wait,必须在同步代码块中

sleep,可以在任何地方睡

4、是否需要捕获异常

wait,不需要捕获一场

sleep,必须要捕获异常

3、Lock锁(重点)

传统synchronized

public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{
            for (int i =0;i<40;i++){
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i =0;i<40;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i =0;i<40;i++){
                ticket.sale();
            }
        },"C").start();
    }
}
class Ticket{
    private int number = 100;
    public synchronized void sale(){
        if(number>0){
 System.out.println(Thread.currentThread().getName() + "抢到第" + (number--) + "张票,剩余" + number + "张票");
        }
    }
}

Lock接口

加锁:.lock

解锁:.unlock

可重入锁:ReentrantLock,ReentrantReadWriteLock.ReadLock(读锁),ReentrantReadWriteLock.WriteLock(写锁)

公平锁:十分公平,先来后到

非公平锁:十分不公平,可以插队(默认)

使用步骤:

  1. new ReentrantLock();
  2. lock.lock();
  3. finally=>lock.unlock();
class Ticket2{

    private int number = 100;
    Lock lock = new ReentrantLock();

    public void sale(){
        lock.lock();
        try {
            if(number>0){
                System.out.println(Thread.currentThread().getName() + "抢到第" + (number--) + "张票,剩余" + number + "张票");
            }
        }catch (Exception e){

        }finally {
            lock.unlock();
        }

    }
}
synchronized和Lock的区别
  1. synchronized是一个内置的java关键字,Lock是一个java类
  2. synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
  3. synchronized会自动释放锁,Lock锁必须要手动释放锁!如果不释放会发生死锁
  4. synchronized 线程1阻塞,线程2会继续等,Lock锁不一定会等待下去
  5. synchronized 可重入锁,不可以中断的,非公平;Lock 可重入锁,可以判断锁,可以自己设置公平或不公平
  6. synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码

锁是什么,如何判断锁的是谁

4、生产者和消费者问题

生产者和消费者问题synchronized版本
class Data{
  private int number = 0;
  public synchronized void increment() throws InterruptedException {
    if(number!=0){
      this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName()+number);
    this.notifyAll();
  }
  public synchronized void decrement() throws InterruptedException {
    if(number==0){
      this.wait();
    }
    number--;
System.out.println(Thread.currentThread().getName()+number);
    this.notifyAll();
  }
}

如果线程更多,会发生问题,会发生 虚假唤醒

if判断需更换为while循环

class Data{
  private int number = 0;

  public synchronized void increment() throws InterruptedException {
    while(number!=0){
      this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName()+number);
    this.notifyAll();
  }
  public synchronized void decrement() throws InterruptedException {
    while(number==0){
      this.wait();
    }
    number--;
    System.out.println(Thread.currentThread().getName()+number);
    this.notifyAll();
  }
}
JUC版本的生产者和消费者

Lock下面的await方法相当于synchronized的wait方法

Lock下面的signal方法相当于synchronized的notify方法

class Data2{
  private int number = 0;
  Lock lock = new ReentrantLock();

  public synchronized void increment() throws InterruptedException {
    lock.lock();
    Condition condition = lock.newCondition();
    try {
      while(number!=0){
        condition.await();
      }
      condition.signalAll();
      number++;
      System.out.println(Thread.currentThread().getName()+number);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  public synchronized void decrement(){
    lock.lock();
    Condition condition = lock.newCondition();
    try {
      while(number==0){
        condition.await();
      }
      condition.signalAll();
      number--;
      System.out.println(Thread.currentThread().getName()+number);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
}
Condition精准的通知和唤醒线程
class Data3 {
  private Lock lock = new ReentrantLock();
  private int number = 1;
  private Condition condition1 = lock.newCondition();
  private Condition condition2 = lock.newCondition();
  private Condition condition3 = lock.newCondition();
  public void printA() {
    lock.lock();
    try {
      while (number != 1) {
        condition1.await();
      }
      System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA");
      number = 2;
      condition2.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  public void printB() {
    lock.lock();
    try {
      while (number != 2) {
        condition2.await();
      }
      System.out.println(Thread.currentThread().getName() + "=>BBBBBBBB");
      number = 3;
      condition3.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  public void printC() {
    lock.lock();
    try {
      while (number != 3) {
        condition3.await();
      }
   System.out.println(Thread.currentThread().getName() + "=>CCCCCCCC");
      number = 1;
      condition1.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
}

5、8锁现象

判断锁的是谁!永远知道什么锁

6、集合类不安全

List不安全

ArrayList在多线程下不安全,可能会出现ConcurrentModificationException异常(并发修改异常)

public class CurrentArrayList1 {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    for(int i =1;i<=10;i++){
      new Thread(()->{
        list.add(UUID.randomUUID().toString().substring(0,4));
        System.out.println(list);
      },String.valueOf(i)).start();
    }
  }
}

解决方案:

//jdk1.0就有的,效率低
List<String> list1 = new Vector<>();

List<String> list1 = Collections.synchronizedList(new ArrayList<>());
//写入时复制 COW 计算机程序设计领域的一种优化策略
//多个线程调用的时候 list在读取的时候是固定的,写入(覆盖)
//相当于在写入的时候避免覆盖,造成数据问题
List<String> list1 = new CopyOnWriteArrayList<>();

CopyOnWriteArrayListVector效率更低,因为Vertor使用了synchronized

Set不安全

与ArrayList同理,同样会出现并发修改异常

public class CurrentHashSet1 {
  public static void main(String[] args) {
    Set<String> set1 = new HashSet<>();
    for (int i = 0;i<100;i++){
      new Thread(()->{
        set1.add(UUID.randomUUID().toString().substring(0,3));
        System.out.println(set1);
      },String.valueOf(i)).start();
    }
  }
}

解决方案:

Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
Set<String> set1 = new CopyOnWriteSet<>();

HashSet底层是什么

public HashSet() {
  map = new HashMap<>();
}
add set 本质就是map key无法重复的!

Map不安全

HashMap底层加载因子0.75,初始大小为1 << 4(16),最大容量1 << 30

多线程下依然不安全,使用ConcurrentHashMap();

java并发处理一个接口 juc java并发_java并发处理一个接口

7、Callable(简单)

Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同
public class CallableTest {
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    new Thread("A").start();
    //        new Thread(new Runnable()).start();
    //        new Thread(new FutureTask<>()).start();
    //        new Thread(new FutureTask<>(Callable)).start();

    MyThread myThread = new MyThread();
    FutureTask futureTask = new FutureTask(myThread);//适配类
    new Thread(futureTask,"B").start();
    Integer o = (Integer)futureTask.get();//可能会产生阻塞,放到最后一行,或者使用异步通信
    System.out.println(o);
  }
}
class MyThread implements Callable<Integer> {

  @Override
  public Integer call() throws Exception {
    System.out.println("Call");
    return 1024;
  }
}

细节:

  1. 有缓存
  2. 结果可能需要等待,会阻塞

8、常用的辅助类

1、CountDownLatch

减法计数器

public class CountDownLatchDemo {
  public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(10);
    for(int i=1;i<=10;i++) {
      countDownLatch.countDown();// 数量减1
      new Thread(() -> {
        System.out.println(Thread.currentThread().getName());
      }, String.valueOf(i)).start();
    }
    countDownLatch.await();//等待计数器归零,再执行下面操作

    System.out.println("Close the door");
  }
}
  • countDownLatch.countDown();// 数量减1
  • countDownLatch.await();等待计数器归零,再执行下面操作

每次有线程调用countDown()方法时数量减1,假设数据变为0,await();方法就会被唤醒,继续执行!

2、SyclicBarrier

加法计数器

public class CycliBarrierDemo {
  public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(7,new Thread(()->{
      System.out.println("召唤神龙");
    }));
    for(int i=1;i<=7;i++){
      new Thread(()->{
        System.out.println("第"+Thread.currentThread().getName()+"颗龙珠");
        try {
          cyclicBarrier.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        } catch (BrokenBarrierException e) {
          e.printStackTrace();
        }
      }, String.valueOf(i)).start();
    }
  }
}

3、Semaphore

Semaphore:信号量

只允许指定数量的线程(限流)

public class SemaphoreDemo {
  public static void main(String[] args) {
    //permits:线程数量
    Semaphore semaphore = new Semaphore(3);
    for (int i = 0; i < 10; i++) {
      new Thread(() -> {
        //acquire :得到
        try {
          semaphore.acquire();
          System.out.println(Thread.currentThread().getName() + "抢到车位");
          TimeUnit.SECONDS.sleep(2);
          System.out.println(Thread.currentThread().getName() + "离开车位");
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          //release :释放
          semaphore.release();
        }
      },String.valueOf(i)).start();
    }
  }
}

acquire(); 获得,假设已经满了进入等待状态,知道有线程被释放才进行获取(阻塞)

release(); 释放,会将当前的信号量释放+1,然后唤醒等待的线程!

作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!

9、读写锁

ReentrantReadWriteLock:可以被多个线程同时读,只能有一个线程写

/**
 * 独占锁(写锁)只能被一个线程占有
 * 共享锁(读锁)多个线程可以同时占有
 * ReadWriteLock
 * 读 - 读 可以共存
 * 读 - 写 不能共存
 * 写 - 写 不能共存
 * @author lee
 * @date 2020/9/27 - 2:17 下午
 */
public class ReentrantReadWriteLockDemo {
  public static void main(String[] args) {
    MyCache myCache = new MyCache();
    for (int i = 1; i < 10; i++) {
      final int temp = i;
      new Thread(()->{
        myCache.get(temp + "");
      },String.valueOf(i)).start();
    }
    for (int i = 1; i < 10; i++) {
      final int temp = i;
      new Thread(()->{
        myCache.set(temp + "","");
      },String.valueOf(i)).start();
    }
  }
}
class MyCache{
  private volatile Map<String,String> map = new HashMap<>();
  private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

  public void get(String key){
    readWriteLock.readLock().lock();
    try {
      System.out.println(Thread.currentThread().getName()  + "写入进入");
      map.get(key);
      System.out.println(Thread.currentThread().getName() + "写入完成");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      readWriteLock.readLock().unlock();
    }

  }
  public void set(String key,String value){
    readWriteLock.writeLock().lock();
    try {
      System.out.println(Thread.currentThread().getName() + "读取进入");
      map.put(key,value);
      System.out.println(Thread.currentThread().getName() + "读取完成");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      readWriteLock.writeLock().unlock();
    }
  }
}

10、阻塞队列

BlockingQueue和List、Set同样都是Conllection下的接口

什么情况下会使用阻塞队列:多线程并发处理,线程池

java并发处理一个接口 juc java并发_并发编程_02

学会使用队列

添加、移除

四组API

  1. 抛出异常
  2. 不会抛出异常
  3. 阻塞等待
  4. 超时等待

方式

抛出异常

有返回值,不抛出异常

阻塞 等待

超时等待

添加

add

offer()

put()

offer(,)

移除

remove

poll()

take()

poll(,)

检测队首元素

element

peak()

抛出异常

public static void test01(){
  ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  System.out.println(blockingQueue.add("a"));
  System.out.println(blockingQueue.add("b"));
  System.out.println(blockingQueue.add("c"));
  //IllegalStateException: Queue full 会抛出异常:队列已满
  System.out.println(blockingQueue.add("d"));

  System.out.println(blockingQueue.remove());
  System.out.println(blockingQueue.remove());
  System.out.println(blockingQueue.remove());
  //NoSuchElementException 没有元素
  System.out.println(blockingQueue.remove());
}

有返回值,不抛出异常

public static void test02(){
  ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

  System.out.println(blockingQueue.offer("a"));
  System.out.println(blockingQueue.offer("b"));
  System.out.println(blockingQueue.offer("c"));

  //返回false,不抛出异常
  System.out.println(blockingQueue.offer("d"));
	//查看队首元素
  System.out.println(blockingQueue.element());
  
  System.out.println(blockingQueue.poll());
  System.out.println(blockingQueue.poll());
  System.out.println(blockingQueue.poll());

  //返回空,不抛出异常
  System.out.println(blockingQueue.poll());
}

阻塞等待

public static void test03() throws InterruptedException {
  ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

  blockingQueue.put("a");
  blockingQueue.put("b");
  blockingQueue.put("c");
  //如果队列已满,会一直等待
  blockingQueue.put("d");

  System.out.println(blockingQueue.take());
  System.out.println(blockingQueue.take());
  System.out.println(blockingQueue.take());

  //如果队列为空,会一直等待
  System.out.println(blockingQueue.take());
}

超时等待

public static void test04() throws InterruptedException {
  ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  blockingQueue.offer("a",2, TimeUnit.SECONDS);
  blockingQueue.offer("b",2, TimeUnit.SECONDS);
  blockingQueue.offer("c",2, TimeUnit.SECONDS);

  //如果等待两秒无法加入,直接跳过
  blockingQueue.offer("d",2, TimeUnit.SECONDS);
  System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
 System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
 System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
  //如果等待两秒无法获取,直接跳过
 System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}

SynchronousQueue(同步队列)

没有容量

进去一个元素,必须等待取出来之后,才能再往里面放一个元素

同步队列实现:

public class SynchronousQueueDemo {
  public static void main(String[] args) {
    //SynchronousQueue和其他的BlockingQueue不一样,它不存储元素,只要put一个元素必须先取出来,否则不能再put
    SynchronousQueue synchronousQueue = new SynchronousQueue();//同步队列
    new Thread(()->{
      try {
        System.out.println(Thread.currentThread().getName() + "put 1");
        synchronousQueue.put("1");
        System.out.println(Thread.currentThread().getName() + "put 2");
        synchronousQueue.put("2");
        System.out.println(Thread.currentThread().getName() + "put 3");
        synchronousQueue.put("3");

      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    },"T1").start();
    new Thread(()->{
      try {
        System.out.println(Thread.currentThread().getName() +"==" +synchronousQueue.take());
        System.out.println(Thread.currentThread().getName() +"==" +synchronousQueue.take());
        System.out.println(Thread.currentThread().getName() +"==" +synchronousQueue.take());

      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    },"T2").start();
  }
}

11、线程池(重点)

线程池:三大方法、七大参数、4种拒绝策略

池化技术

程序的运行,本质:占用系统的资源!优化资源的使用!=》池化技术

线程池、连接池、内存池、对象池、、、、、、

池化技术:实现准备好一些资源,有人要用,就来池子里面拿,用完还回来

线程池的好处:

  1. 降低资源的消耗
  2. 提高响应的速度
  3. 方便管理

线程服用、可以控制最大并发数、管理线程

线程池三大方法

/**
 * Executor 工具类,三大方法
 * 使用了线程池之后,要使用线程池创建线程
 *
 * @author lee
 * @date 2020/9/28 - 7:14 下午
 */
public class poolDemo01 {
  public static void main(String[] args) {
    //        ExecutorService executorService = Executors.newSingleThreadExecutor();//单个线程
    //        ExecutorService executorService = Executors.newFixedThreadPool(6);//创建一个固定的线程池的大小
    ExecutorService executorService = Executors.newCachedThreadPool();//可伸缩的线程池

    try {
      for (int i = 0; i < 100; i++) {
        //使用了线程池之后,要使用线程池创建线程
        executorService.execute(()->{
          System.out.println(Thread.currentThread().getName());
        });
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      //线程池用完要关闭
      executorService.shutdown();
    }
  }
}

七大参数

源码分析

public static ExecutorService newSingleThreadExecutor() {
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                            0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newFixedThreadPool(int nThreads) {
  return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                60L, TimeUnit.SECONDS,
                                new SynchronousQueue<Runnable>());
}

//本质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;
}
  • int corePoolSize,//核心线程池大小
  • int maximumPoolSize,//最大线程池大小
  • long keepAliveTime,//超时了没有人调用就会释放
  • TimeUnit unit,//超时单位
  • BlockingQueue workQueue,//阻塞队列
  • ThreadFactory threadFactory//线程工厂,创建线程的,一般不用动
  • RejectedExecutionHandler handler//拒绝策略

手动创建一个线程池

public class poolDemo02 {
  public static void main(String[] args) {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
      2,  //默认容量
      5,//最大可处理并发量
      3,  //3秒没有使用,就关闭开放的扩展线程池
      TimeUnit.SECONDS,//单位 ,秒
      new LinkedBlockingDeque<>(3),
      Executors.defaultThreadFactory(),
      new ThreadPoolExecutor.AbortPolicy());
    try {
      for (int i = 1; i <= 8; i++) {
        //使用线程池创建线程
        threadPool.execute(()->{
          System.out.println(Thread.currentThread().getName() + "ok");
        });
      }
    }catch (Exception e){
      e.printStackTrace();
    }finally {
      //使用完毕后关闭线程池
      threadPool.shutdown();
    }
  }
}

四种拒绝策略

/**
 * ThreadPoolExecutor.AbortPolicy()             满了,还有人进来,不处理,直接报异常
 *  ThreadPoolExecutor.CallerRunsPolicy()       哪来的回哪去
 *  ThreadPoolExecutor.DiscardOldestPolicy()    队列满了,尝试丢掉最早的任务,不会抛出异常
 *  ThreadPoolExecutor.DiscardPolicy()          队列满了,丢掉任务,不会抛出异常
 * @author lee
 * @date 2020/9/30 - 8:31 上午
 */

小结

最大线程如何定义

  1. CPU密集型,几核就是几,可以保持CPU的效率最高
  2. IO密集型 判断你程序中十分耗IO的线程,设置大于这个线程的数量

12、四大函数式接口(必须掌握)

四大函数式接口:Function、Predicate、Consumer、Supplier

新时代的程序员:lambda表达式、链式编程、Stream流式计算

函数式接口:

只有一个方法的接口,如:Runnable,简化编程模型,在新版本的框架底层大量应用,foreach(消费者类的函数式接口)

Function 函数型接口

有一个输出参数,有一个输出参数

只要是函数式接口 就可以用lambda表达式简化

java并发处理一个接口 juc java并发_并发编程_03

代码测试:

//普通写法
Function function = new Function<String,String>(){
  @Override
  public String apply(String o) {
    return o;
  }
};
System.out.println(function.apply("asd"));
//------------------------------------------
//lambda表达式写法
Function<String,String> function1 = (str)->{return str;};
System.out.println(function1.apply("aaa"));

Predicate 断定型接口

有一个输入参数,返回值只能是boolean值

java并发处理一个接口 juc java并发_多线程_04

代码测试:

//普通写法
Predicate<String> stringPredicate = new Predicate<String>() {
  @Override
  public boolean test(String o) {
    return o.isEmpty();
  }
};
System.out.println(stringPredicate.test("s"));

//lambda表达式写法
Predicate<String> stringPredicate1 = (str)->{
  return str.isEmpty();
};
System.out.println(stringPredicate1.test(""));

Consumer 消费型接口

只有输入,没有返回值

java并发处理一个接口 juc java并发_java_05

代码测试:

//普通写法
Consumer<String> objectConsumer = new Consumer<String>() {
  @Override
  public void accept(String o) {
    System.out.println(o);
  }
};
objectConsumer.accept("hello world");
//lambda表达式写法
Consumer<String> objectConsumer1 = (str)->{ System.out.println(str);};
objectConsumer1.accept("hello world hello");

Supplier 供给型接口

没有参数,只有返回值

java并发处理一个接口 juc java并发_System_06

代码测试:

//普通写法
Supplier<Integer> objectSupplier = new Supplier<Integer>() {
  @Override
  public Integer get() {
    System.out.println("get()");
    return 1024;
  }
};
System.out.println(objectSupplier.get());
//lambda表达式写法
Supplier<Integer> objectSupplier1 = ()->{ System.out.println("get()"); return 1024;};
System.out.println(objectSupplier1.get());

13、Stream流式计算

Java.util.stream

什么是Stream流式计算

大数据:存储+计算

存储:集合、Mysql 本质是用来存东西的

计算都应该交给流来做

代码测试:

public static void main(String[] args) {
  User user1 = new User(1,"a",10);
  User user2 = new User(2,"b",10);
  User user3 = new User(3,"c",10);
  User user4 = new User(4,"d",25);
  User user5 = new User(5,"e",22);
  User user6 = new User(6,"f",30);
  //集合就是存储数据的
  List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6);
  //计算交给stream流
  //lambda表达式、链式编程、函数式接口、Stream流式计算
  list.stream()
    .filter(u->{return u.getId()%2==0;})
    .filter(u->{return u.getAge()>23;})
    .map(u->{return u.getName().toUpperCase();})
    .sorted((u1,u2)->{return u2.compareTo(u1);})
    .limit(1)
    .forEach(System.out::println);
  ;
}

14、ForkJoin

什么是ForkJoin

ForkJoin在JDK1.7,执行并行任务!提高效率,大数据量!

大数据:Map Reduce(把大任务拆分为小任务)

ForkJoin特点:工作窃取

这里面维护的都是双端队列,B处理完会帮A处理

ForkJoin

代码实现:

/**
 * @author lee
 * @date 2020/9/30 - 2:29 下午
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
  private long start;
  private long end;

  private long temp = 10000L;

  public ForkJoinDemo(Long start, Long end) {
    this.start = start;
    this.end = end;
  }

  @Override
  protected Long compute() {

    if ((end - start) > temp) {
      //forkJoin
      long middle = (start + end) / 2;//中间值
      ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
      task1.fork();//把任务压入线程队列
      ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
      task2.fork();//把任务压入线程队列
      return task1.join() + task2.join();
    } else {
      Long sum = 0L;
      for (Long i = start; i <= end; i++) {
        sum += i;
      }
      return sum;
    }
  }
}

测试:

//普通程序员
//结果:500000000500000000
//时间:439ms
public static void test01(){
  long sum = 0;
  long start = System.currentTimeMillis();
  for (long i = 0; i <= 10_0000_0000; i++) {
    sum += i;
  }
  System.out.println(sum);
  long end = System.currentTimeMillis();
  System.out.println("时间:" + (end-start) + "ms");
}
//使用forkJoin
//结果:500000000500000000
//时间:4721ms
public static void test02() throws ExecutionException, InterruptedException {
  long start = System.currentTimeMillis();

  ForkJoinPool forkJoinPool = new ForkJoinPool();
  ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
  ForkJoinTask<Long> submit = forkJoinPool.submit(task);
  Long sum = submit.get();

  System.out.println(sum);
  long end = System.currentTimeMillis();
  System.out.println("时间:" + (end-start) + "ms");
}
//Stream并行流计算
//结果:500000000500000000
//时间:359ms
public static void test03(){
  long start = System.currentTimeMillis();

  long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);

  System.out.println(sum);
  long end = System.currentTimeMillis();
  System.out.println("时间:" + (end-start) + "ms");
}

15、异步回调*

future设计的初衷:对将来的某个事件的结果进行建模

16、JMM(Java内存模型)

JMM:Java Memory Model 是一个概念!约定

关于JMM的一些同步的约定:

  1. 线程解锁前 必须把共享变量立刻写回主存!
  2. 线程加锁前 必须读取主存中的最新值到工作内存中!
  3. 加锁和解锁是同一把锁

线程 工作内存、主内存

8种操作:

java并发处理一个接口 juc java并发_System_07

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
  • unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

(1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。

(2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

(3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。

(4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。

(5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。

(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

(7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。

(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。

存在问题:线程B修改了内存,线程A不能及时的可见

17、volatile

volatile是java虚拟机中提供轻量级的同步机制

  1. 保证可见性
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {

  new Thread(()->{
    while(num == 0){
      System.out.println(num);
    }
  }).start();

  TimeUnit.SECONDS.sleep(2);
  num = 1;
  System.out.println(num);
}
  1. 不保证原子性
    原子性:线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
/**
 * volatile不保证原子性
 * @author lee
 * @date 2020/10/1 - 3:18 下午
 */
public class volatileDemo02 {
  public volatile static int num;
  public static void add(){
    num++;
  }
  public static void main(String[] args) {
    for (int i = 0; i < 20; i++) {

      new Thread(()->{
        for (int j = 0; j < 1000; j++) {
          add();
        }
      }).start();
    }
    System.out.println(num);
  }
}

通过反编译可知,在num++操作时存在3步,获得值、+1、写回,所以这个操作不是一个原子性操作,而volatile不保证原子性,一般使用原子类解决原子性问题

**原子类:**原子类的底层都和操作系统有关,比synchronized和Lock高效很多

//使用原子类AtomicInteger
public volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
  //        num++;
  num.getAndIncrement(); //执行AtomicInteger + 1 操作
}
public static void main(String[] args) {
  for (int i = 0; i < 20; i++) {

    new Thread(()->{
      for (int j = 0; j < 1000; j++) {
        add();
      }
    }).start();
  }
  System.out.println(num);
}
  1. 禁止指令重排
    指令重排:你写的程序,计算机并不是按照你写的那样去执行的
    源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行
    处理器在进行指令重排的时候,回考虑到数据之间的依赖性
    在多线程下可能会出现问题
    内存屏障。CPU指令。作用:
  1. 保证特定的操作的执行顺序
  2. 可以保证某些变量的内存可见性

加volatile会在volatile方法的上面和下面都加上一层内存屏障,来禁止指令重排

18、彻底玩转单例模式

饿汉式 DCL懒汉式 枚举

19、深入理解CAS

什么是CAS: Comapre and Swap(比较并交换)CAS是CPU的并发原语

Java无法操作内存,Java可以通过native方法调用C++,C++可以操作内存

java并发处理一个接口 juc java并发_java并发处理一个接口_08

大厂必须要深入研究底层!有所突破

**getAndIncrement方法底层:**自旋锁

java并发处理一个接口 juc java并发_java并发处理一个接口_09

CAS:比较当前内存中的值和主内存中的值,如果这个值是期望的,那么执行操作!如果不是就一直循环

CAS缺点:

  1. 由于底层是自旋锁,循环会耗时
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题

CAS:ABA问题(狸猫换太子)

A在操作数据i时,B在中间将i改变了值并且又改了回来

/**
 * @author lee
 * @date 2020/10/1 - 5:13 下午
 */
public class CASDemo02 {
  //CAS
  public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(2020);
    //乐观锁:
    //public final boolean compareAndSet(int expect, int update)
    //两个参数,期望,更新,如果期望值达到了,就更新,返回true,否则返回false
    //=====================捣乱的线程========================
    System.out.println(atomicInteger.compareAndSet(2020, 2021));
    System.out.println("2->" +atomicInteger);
    System.out.println(atomicInteger.compareAndSet(2021, 2020));
    System.out.println("3->" +atomicInteger);

    //=====================期望的线程========================
    System.out.println(atomicInteger.compareAndSet(2020, 2333));
    System.out.println("4->" +atomicInteger);
  }
}

20、原子引用

解决ABA问题,引入原子引用,对应思想–乐观锁

带版本号的原子操作

大坑:Integer使用了对象缓存机制,默认范围时-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间

/**
 * @author lee
 * @date 2020/10/1 - 5:13 下午
 */
public class CASDemo03 {
  //CAS
  public static void main(String[] args) {
    //        AtomicInteger atomicInteger = new AtomicInteger(2020);
    AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(100,1);
    int stamp = atomicInteger.getStamp();
    new Thread(()->{
      try {
        TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(atomicInteger.compareAndSet(100, 110, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
      System.out.println(atomicInteger.getStamp());
      System.out.println(atomicInteger.compareAndSet(110, 100, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
      System.out.println(atomicInteger.getStamp());
    },"a").start();

    //乐观锁的原理相同
    new Thread(()->{
      try {
        TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(atomicInteger.compareAndSet(100, 110, stamp, stamp + 1));
      System.out.println(atomicInteger.getStamp());

    },"b").start();
  }
}

各种锁

公平锁:非常公平,先来后到,不能插队

非公平锁:非常不公平,可以插队(默认都是非公平的)

public ReentrantLock() {
  sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

可重入锁

可重入锁:递归锁,拿到了外面的锁自动获得里面的锁

Synchronized:

public class Demo01 {
  public static void main(String[] args) {
    Phone phone = new Phone();
    new Thread(()->{
      phone.smsg();
    }).start();
    new Thread(()->{
      phone.smsg();
    }).start();
  }
}
class Phone{
  public synchronized void smsg(){//一把锁
    System.out.println(Thread.currentThread().getName() + "发短信");
    call();
  }
  public synchronized void call(){
    System.out.println(Thread.currentThread().getName() + "打电话");
  }
}

Lock:

/**
 * @author lee
 * @date 2020/10/2 - 10:02 上午
 */
public class Demo02 {
  public static void main(String[] args) {
    Phone2 phone = new Phone2();
    new Thread(()->{
      phone.smsg();
    }).start();
    new Thread(()->{
      phone.smsg();
    }).start();
  }
}
class Phone2{
  Lock lock = new ReentrantLock();
  public void smsg(){
    lock.lock();//两把锁
    try {
      System.out.println(Thread.currentThread().getName() + "发短信");
      call();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  public void call(){
    lock.lock();
    try {
      System.out.println(Thread.currentThread().getName() + "打电话");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
}

自旋锁

spinLock

自定义一个自旋锁:

/**
 * 自旋锁
 * @author lee
 * @date 2020/10/2 - 10:26 上午
 */
public class SpinLockDemo01 {
  AtomicReference<Thread> atomicReference = new AtomicReference<>();
  //加锁
  public void lock(){
    Thread thread = Thread.currentThread();
    System.out.println(Thread.currentThread().getName() + "加锁");
    while(!atomicReference.compareAndSet(null,thread)){

    }
  }
  //解锁
  public void unlock(){
    Thread thread = Thread.currentThread();
    System.out.println(Thread.currentThread().getName() + "解锁");
    atomicReference.compareAndSet(thread,null);
  }
}

测试:

/**
 * 测试自旋锁
 * @author lee
 * @date 2020/10/2 - 12:03 下午
 */
public class TestSpinLock {
  public static void main(String[] args) {

    //底层使用的自旋锁CAS
    SpinLockDemo01 lock = new SpinLockDemo01();
    new Thread(() -> {
      lock.lock();
      try {
        TimeUnit.SECONDS.sleep(2);
      } catch (Exception e) {
        e.printStackTrace();
      } finally {

        lock.unlock();
      }
    }, "T1").start();

    new Thread(() -> {
      lock.lock();
      try {

      } catch (Exception e) {
        e.printStackTrace();
      } finally {
        lock.unlock();
      }
    }, "T2").start();
  }
}

死锁

什么是死锁:A持有a锁,B持有b锁,A和B都试图获取对方的锁

代码实现死锁:

/**
 * 死锁
 * @author lee
 * @date 2020/10/2 - 12:20 下午
 */
public class DeadLockDemo {
  public static void main(String[] args) {
    String lockA = "lockA";
    String lockB = "lockB";
    new Thread(new MyThread(lockA,lockB)).start();
    new Thread(new MyThread(lockB,lockA)).start();
  }
}
class MyThread implements Runnable{
  private String lockA;
  private String lockB;

  public MyThread(String lockA, String lockB) {
    this.lockA = lockA;
    this.lockB = lockB;
  }
  @Override
  public void run() {
    synchronized (lockA){
      System.out.println(Thread.currentThread().getName() +"获取到" +lockA +"想获取"+ lockB);
      try {
        TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      synchronized (lockB){
        System.out.println(Thread.currentThread().getName() +"获取到" +lockB +"想获取"+ lockA);
      }
    }
  }
}

解决死锁:

  1. 使用jps定位进程号

java并发处理一个接口 juc java并发_多线程_10

  1. 使用jstack + 进程号查看进程信息

面试或工作中排查问题:

  1. 日志
  2. 堆栈信息