Juc并发编程

多线程进阶=>JUC并发编程

1. 线程与进程

并发、并行

并发编程:并发、并行

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

  • CPU一核,模拟出来多个线程,快速交替

并行

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

2. lock 锁

synchronized 与 lock 区别

  1. synchronized 内置的 java 关键字, Lock 是一个类
  2. synchronized 无法获取锁的状态,Lock 可以判断是否获取到了锁
  3. synchronized 会自动释放锁,lock 必须手动释放锁
  4. synchronized 可重入锁,不可以中断,非公平; lock,可重入锁,可以判断锁,非公平(可以自己设置)
  5. synchronized 适合少量的代码同步问题,lock 适合锁大量的同步代码。

锁是什么,如何判断锁

3.生产者消费者

package JUC;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * A执行完调用B,B执行完调用C,C执行完调用A
 * @author guojingbo
 * @date 2021-09-24 15:56
 * @param null
 * @return
 */
public class pc {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.printC();
            }
        },"C").start();
    }

}
class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1;
    public void printA(){
            lock.lock();
        try {
        //业务->判断->执行->通知
            while (number!=1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>AAAAAAAA");
            //唤醒指定的人,B
            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()+"=>BBBBBBBBB");
            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()+"=>CCCCCCCCCCCC");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

4.8锁现象

如何判断锁的是谁!永远知道什么是锁,锁到底锁的是谁!

深刻理解我们的锁

package JUC.lock8;

import java.util.concurrent.TimeUnit;
/**
 * 8锁就是关于锁的八个问题
 * 1. 标准问题,两个线程先打印还是发短信? 1/发短信 2/打电话 第一个线程先开启
 * 2.sendSms延迟 4 秒,两个线程先打印还是发短信? 1/发短信 2/打电话  两个线程先快先执行
 * 3. 先打印发短信还是 hello 1/hello 2/发短信  hello 没有加 synchronized 关键字
 * 4. 两个对象第一个 sendSms 第二个 call 哪个先 1.打电话 2、发消息 因为两个对象锁,并且打电话执行的更快
 * 5. 两个静态方法两个对象顺序一致哪个先 1.发消息 2.打电话 静态方法抢类锁,就算两个对象也还是一个类锁
 * 6.1个静态同步方法普通的同步方法,一个对象 1\打电话 2\发短信 因为抢的锁不同一个抢类锁一个抢对象锁
 * @author guojingbo
 * @date 2021-09-24 16:34
 *
 * @return
 */
public class Phone {
    public static void main(String[] args) {
        int[] ints = new int[128];

        String a ="0";
        char c = a.charAt(0);
        ints[a.charAt(0)]++;
//        ints[a.charAt(0)]=ints[a.charAt(0)]+1;
        for (int anInt : ints) {
            System.out.println(anInt);
        }
        System.out.println(c);

        Test1 test = new Test1();
        new Thread(()->{
            test.sendSms();
        }).start();
        //捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            test.call();
        }).start();
    }
}
class Test1{
    //synchronized 锁的对象是锁的调用者
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public  synchronized void call(){
        System.out.println("打电话");
    }
    public void hello(){
        System.out.println("hello");
    }
}

5.集合类不安全

package arithemetic;
import java.util.concurrent.CopyOnWriteArrayList;
public class Main {
//    ConcurrentModificationException 并发修改异常
    /*
    解决方案
    1. List<String> list = new Vector<>();
    2. List<String> list = Collections.synchronizedList(new ArrayList<>());
    3. List<String> list = new CopyOnWriteArrayList<>();
    * */
    //copyOnWrite 写入时复制 COW 计算机设计领域的一种忧化策略
    //多个线程调用的时候,list 读取的时候,固定的,写入覆盖
    //在写入的时候避免覆盖,造成数据问题
    public static void main(String[] args) throws IOException, InterruptedException {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

学习策略:1.先会用,2.货比三家,3.研究源码

package JUC;

import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 同理: concurrentModificationException
 * 解决方案
 *     1. Set<String> set = Collections.synchronizedSet(new HashSet<>()) ;
 *     2. Set<String> set = new CopyOnWriteArraySet();
 * @author guojingbo
 * @date 2021-09-24 19:58
 * @return
 */
public class setTest {
    public static void main(String[] args) {
        Set<String> set = new CopyOnWriteArraySet();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }

}

在这里插入图片描述

ConcurrentHashMap 不能接受 null的 key 和 null 的 value 会抛出空指针异常

6.Callable(简单)

在这里插入图片描述

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同,run(),call()
new Thread(new Runnable).start()
new Thread(new  FutureTask()).start()
new Thread(new FutureTask(Callable)).start
package JUC.coallable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class callableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        new Thread(new MyThread()).start();
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();//结果会被放入缓存提高效率
        String o =(String) futureTask.get();//这个 get 方法可能会产生阻塞,一般将其放在最后
        System.out.println(o);
    }
}
class MyThread implements Callable<String> {


    @Override
    public String call() throws Exception {
        return "234";
    }
}

细节:

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

7.常用的辅助类(重要)

7.1 CountDownLatch

在这里插入图片描述

package JUC.add;

import java.util.concurrent.CountDownLatch;

// 计数器
public class countDownLatchDemo {
    public static void main(String[] args) {
        // 必须要执行的任务的时候使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Get Out");
                countDownLatch.countDown();//数量减一
            },String.valueOf(i)).start();
        }
        try {
            countDownLatch.await();//等待计数器归零,然后再向下执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Close Door");

    }
}

减法计数器

每次有线程调用 countDown 数量减一,假设计数器变为0 ,countDownLatch.await 调用这个函数后会判断是等于 0 如果不等于0会被阻塞,如果等于 0 会继续执行

指定线程执行完毕,再执行操作

7.2 CyclicBarrier

在这里插入图片描述

加法计数器

package JUC.add;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐七颗龙珠召唤神龙
         * @author guojingbo
         * @date 2021-09-25 09:29
         * @param args
         * @return void
         */

        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功");
        });
        for (int i = 0; i < 7; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

CyclicBarrier 执行达到指定线程数再执行操作

7.3 Semaphore

在这里插入图片描述

Semaphore: 同一时间只能有指定数量个得到线程

public class SemaphoreDemo {
    public static void main(String[] args) {
        //线程数量:限流
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{

                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 {
                    semaphore.release();
                }
            }).start();
        }
    }
}

semaphore.acquire()获得,假设已经满了,等待,直到有资源被释放

semaphore.release()释放,将当前占有的信号量释放

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

8.读写锁

ReadWriteLock

在这里插入图片描述

/**
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 一次能被多个线程占有
 * ReadWriteLock
 * 读-读   可以
 * 写-读  不可以
 * 写-写  不可以
 * @author guojingbo
 * @date 2021-09-25 10:32
 * @return
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        for (int i = 0; i < 5; i++) {
            final int temp  = i;
            new Thread(() -> {
                myCache.put(temp+"",temp+"");
            }, String.valueOf(temp)).start();
        }
        for (int i = 0; i < 5; i++) {
            final int temp  = i;
            new Thread(() -> {
                myCache.get(temp+"");
            }, String.valueOf(temp)).start();
        }

    }
}

class MyCacheLock {
    private Map<String, String> resource = new HashMap<String, String>();
    //读写锁
    private ReentrantReadWriteLock lock  = new ReentrantReadWriteLock();
    //写
    public void put(String key, String value) {
       lock.writeLock().lock();

        try{
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            resource.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完毕");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }

    }

    //读
    public void get(String key) {
        lock.readLock().lock();

        try{
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            resource.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完毕");
        }catch (Exception e){
            e.printStackTrace();
        }finally {

        }

    }
}
class MyCache {
    private Map<String, String> resource = new HashMap<String, String>();

    //写
    public void put(String key, String value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        resource.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完毕");
    }

    //读
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        resource.get(key);
        System.out.println(Thread.currentThread().getName() + "读取完毕");
    }
}

8.阻塞队列

如果队列满了会阻塞

如果队列是空的,必须阻塞生产

在这里插入图片描述

什么情况我们会使用阻塞队列:多线程并发处理,线程池!
在这里插入图片描述

学会使用队列

添加、移除

四组API

方式抛出异常有返回值阻塞等待超时等待
添加addofferputoffer(内容,时间,单位)offer("D",2,TimeUnit.SECONDS)
移除removepooltakepool (时间,单位)
判断队列头elementpeek
/*抛出异常
     * @author guojingbo
     * @date 2021-09-25 11:31
     *
     * @return void
     */
    public static void test1(){
        //队列的大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
    }
}

SynchronousQueue 同步队列

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

和其他的 BlockingQueue 不一样,SynchronousQueue 不存储元素 put 了一个元素,必须从里面先 take 取出来,否则不能再 Put 值

9、线程池

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

线程池的好处

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

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

在这里插入图片描述

七大参数

    public ThreadPoolExecutor(int corePoolSize, //核心线程池的大小
                              int maximumPoolSize, //最大核心池大小
                              long keepAliveTime, //超时了没有人调用就会释放
                              TimeUnit unit, // 超时单位
                              BlockingQueue<Runnable> workQueue,// 阻塞队列
                              RejectedExecutionHandler handler) { //拒绝策略
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory()//线程工厂,一般不用动
             , handler);
    }

四种策略

在这里插入图片描述

/**
 * @author guojingbo
 * @date 2021-09-25 15:19
 * @return
 * AbortPolicy()   //满了如果还有人进来,就不处理这个人的,并且抛出异常
 * CallerRunsPolicy() //哪来的去哪里
 * DiscardPolicy() //队列满了,不会抛出异常,丢弃任务
 * DiscardOldestPolicy()//队列满了,尝试和最早的竞争,也不会抛出异常
 */

小结与拓展

调优

/*
最大线程该如何定义
1. cpu 密集型 8条线程同时执行,保证 cpu 效率最高 Runtime.getRuntime().availableProcessors()
2. io 密集型 >判断你的程序中十分耗费 io 的线程 一般为这个数量的两倍
// 一个程序15个大型任务
* */

四大函数式接口

函数式接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
//foreach  
  • Consumer : 消费型接口

    • void accept(T t);
  • Supperlier:借给型接口

    • T get() 经过一系列的处理返回一个与原来一样类型的变量
  • Function<T,R> : 函数型接口

    • R apply(T t) 输入T 返回 R
  • Predicate: 断定型接口

    • boolean test(T t) 有一个输入参数,返回值只能是布尔值

10. ForkJoin

什么是 ForkJoin

ForkJoin 在JDK 1.7,并行执行任务来提高效率的,大数据量!

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

使用Stream 流

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

11. 异步回调

Future 是一个接口 CompletableFuture 是其一个实现类

  //异步回调
//        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+"runAsYnc");
//        });
//      future.get() 有返回值的异步回调
        CompletableFuture<Integer> uCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName()+"runAsYnc");
            return 1024;
        });
        uCompletableFuture.whenComplete((t,u)->{
            System.out.println(t+"=>");// 正常的返回结果
        }).exceptionally((e)->{
            String message = e.getMessage();// 不正常的返回结果
            return 404;
        });

12.JMM

淡淡对 Volatile 的理解

Volatile 是 Java 提供轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

什么是JMM

JMM 是java 内存模型,不存在的东西,一个概念!约定

关于JMM 一些同步的约定

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

在这里插入图片描述

在这里插入图片描述

2、不保证原子性

原子性:不可分割

线程A 在执行的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败

如果不加 lock 与 sychronized 怎么保证原子性

使用原子类解决这个原子性问题,这些类底层都直接与操作系统挂钩!在内存中修改值。

3、指令重排

什么是指令重排

源代码->编译器忧化的重排->指令并行也可能有一个重排->内存系统也会重排->执行

处理器在指令重排的时候会考虑指令之间的依赖性

volatile 可以避免指令重排

内存屏障。CPU指令,作用:

  1. 保证特定的操作执行顺序
  2. 保证一些变量内存的可见性

13、彻底玩转单例模式

饿汉式 DCL懒汉式 ,深究!

饿汉式

package single;

public class Hungry {
    private Hungry(){

    }
    private final static Hungry HUNGRY = new Hungry();
    public static Hungry getInstance(){
        return HUNGRY;
    }
}

懒汉式

package single;

public class LazyMan {
    private LazyMan(){}
    private volatile static LazyMan lazyMan;
    // 双重检测 DCL 懒汉式
    public static LazyMan getInstance(){
        if(lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan = new LazyMan();
                    /**
                     * 1. 分配内存空间
                     * 2.执行构造方法
                     * 3.将这个对象指向这个空间
                     *  1->2->3
                     *  1->3->2
                     *  所以我们需要将这个对象加上 volatitle 保证指令不被重排
                     */
                }
            }
        }
        return lazyMan;
    }
    // 多线程并发

}

enum

package single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public enum  EnumSigle {
    INSTANCE;
    public EnumSigle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSigle instance = EnumSigle.INSTANCE;
        Constructor<EnumSigle> declaredConstructor = EnumSigle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSigle instance1 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance);
    }
}

14.深入理解CAS

什么是CAS

compareAndSet :比较并交换

java 无法操作内存 Java 可以调用 c++ c++可以调用内存,Unsafe 类就是java 的后门,通过这个类操作内存

CAS 是CPU 的并发原语

unSafe 类

在这里插入图片描述

在这里插入图片描述

CAS ABA问题(狸猫换太子)

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

缺点:

  1. 循环会耗时
  2. 一次只能保证一个共享变量的原子性
  3. ABA 问题

15.原子引用

对应的思想:乐观锁

带版本号的原子操作!

public class CASDemo {
    //如果泛型是一个包装类,注意对象的引用问题
    public static void main(String[] args) {
        AtomicStampedReference<Object> atomicStampedReference = new AtomicStampedReference<>(1, 1);
        atomicStampedReference.compareAndSet
                (2020, 2022, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
    }
}

16、各种锁的理解

1. 公平锁、非公平锁

公平锁:不能够插队,必须先来后到

非公平锁: 可以插队,cpu说的算

在这里插入图片描述

2.可重入锁

拿到外面的锁,也就拿到了里面的锁

3.自旋锁

在这里插入图片描述

package JUC.spinlock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 *
 * @author guojingbo
 * @date 2021-09-27 14:36
 * @return
 */
public class SplinlockDemo {
    AtomicReference<Thread>atomicReference = new AtomicReference<>();

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"==>lock");
        // 自旋锁
        while (atomicReference.compareAndSet(null,thread)){

        }
    }
    public void myUnlock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"====>MyUnlock");
        atomicReference.compareAndSet(thread,null);
    }
}class Test{
    public static void main(String[] args) throws InterruptedException {
        SplinlockDemo lock = new SplinlockDemo();
        new Thread(()->{
                lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
            }
        },"T1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
            }
        },"T2").start();

    }
}

4.死锁

解决

  1. 使用jps -l定位进程号
    在这里插入图片描述

  2. 使用jstack进程号 找到死锁问题
    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值