java之彻底搞懂并发编程、juc

`## 并发编程

1. 什么是juc

java.util.concurrent在并发编程中使用的工具类

2. 线程基础

2.1 进程和线程
  • 进程

    一个程序,QQ.exe

    一个进程往往可以包含多个线程

    java有多个线程? 2个 main GC

  • 线程

    比如安全管家是一个进程,那么杀毒、垃圾清理都是一个线程

2.2 并发和并行

Erlang 之父 Joe Armstrong 用一张5岁小孩都能看懂的图解释了并发与并行的区别

并行并发图

并发是两个队列交替使用一台咖啡机

并行是两个队列同时使用两台咖啡机

串行是一个队列只能使用一台咖啡机顺序执行

并发是不是一个线程,并行是一个线程?

答:并行和并发都可以是多个线程,就看这些线程能否同时被多个cpu执行,如果可以就是并行,而并发是多个线程被一个cpu轮流切换执行

2.3 线程的几个状态
// Thread.state 枚举类
public enum State {
  			// 新生
        NEW,
				//运行
        RUNNABLE,
				//阻塞
        BLOCKED,
				//等待
        WAITING,
				//超时等待
        TIMED_WAITING,
				//终止、停止
        TERMINATED;
}
2.5 wait/sleep区别
  1. 来自不同的类

    wait => Object

    sleep => Thread

  2. 关于锁的释放

    wait会释放锁

    sleep不会释放锁

  3. 使用返回不同

    wait 只能在同步代码块中使用

    sleep 可以在任何地方使用

  4. 是否需要捕获异常

    wait 不需要捕获

    sleep 必须捕获

2.6 Synchronzed和lock的区别
  1. Synchonized是java关键字, lock是一个java类

  2. Synchonized 无法判断获取锁的状态,lock可以判断是否获取到了锁

  3. Synchonized 会自动释放锁 , lock需要手动释放锁!如果不释放锁会造成死锁

  4. Synchonized 线程1(获得锁,阻塞),线程2(等待); lock就不一定会等在下去 lock.tryLock()

  5. 可重入锁,不可中断的,非公平; lock,可重入锁,可以判断锁,非公平(可以自己设置)

  6. Synchonized 适合锁少量的代码同步问题, lock适合锁大量的同步代码

2.7 创建线程的方式
  • 继承Thread

  • 实现Runable接口

  • 实现Callable接口

    实现Runable和Callable接口的类只能被当成一个 在线程中运行的任务,不是真正意思上的线程,因此最终还是要通过Thread来调用。

2.8 线程创建-接口VS实现类

实现接口要好一些因为:

  • java不支持多继承,因此集成Thread的类无法继承别的类,但是可以实现多个接口
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。

3. Snchronized和Lock

java提供了两种锁机制来控制多个线程对共享资源的互斥访问。第一个是JVM的Synchronized关键字,另一个是jdk实现的ReentrantLock

3.1 synchronized

synchronized关键字可以修饰代码块、一个方法、一个类、一个静态方法

public synchronized void func () {
    // ...
}
3.2 ReentrantLock

ReentrantLock是java.util.concurrent(J.U.C)中的锁

public class ReentrantLockDemo {

    private Lock lock = new ReentrantLock();

    public void func() {
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        } finally {
            lock.unlock(); // 确保释放锁,从而避免发生死锁。
        }
    }
}
3.3 区别
  1. 锁的实现

    Synchronized是JVM实现的是一个关键字,ReentrantLock是jdk实现的

  2. 性能

    新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

  3. 等待可中断

    当持有锁的线程长时间不释放锁的时候,正在等待的其它线程可以选择放弃等待,改为处理其它事情

  4. 公平锁非公平锁

    公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时候来顺序执行的。 java中一般都是非公平锁。

    列: 如果两个线程一个执行10分钟,另一个执行1秒钟情况呢? 肯定使用非公平锁最好喽

    synchornized是非公平锁,ReentrantLock默认也是非公平锁,但是可以改为公平锁

  5. 锁绑定多个条件

    一个 ReentrantLock 可以同时绑定多个 Condition 对象。

线程之间的协作:

wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

await() signal() signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

4. JMM、volatile

在说volatile之前需要先了解一下JMM(java内存模型)

4.1 并发编程的3个基本概念
  1. 原子性

定义: 即一个操作或者多个操作,要么全部执行并且执行过程中不被任何因素打断,要么就都不执行

  1. 可见性

定义: 即多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程能够立刻看到修改的值

  1. 有序性

定义: 即程序执行的顺序按照代码的先后顺序执行

4.2 JMM

JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。

image-20211207125040101

对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程模型中的不可见性问题,较粗暴的方式自然就是加锁,但是此处使用synchronized或者Lock这些方式太重量级了,比较合理的方式其实就是volatile。

4.3 volatile
  1. 保证可见性

  2. 不保证原子性

  3. 禁止指令重排

5. JUC Lock汇总

5.1 Condition 接口

condition是在java1.5中才出现的,它是用来替代传统Object的wait(),notify()实现线程之间的协作的,相对使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效

Condition是个接口,基本的方法就是await()signal()方法;
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
调用Conditionawait()signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
Conditon中的await()对应Objectwait()Condition中的signal()对应Objectnotify()Condition中的signalAll()对应ObjectnotifyAll()
5.2 Lock 接口

java中有两种方式实现同步访问, 第一是使用synchronized关键字,另一种方式就是在java.util.concurrent.locks包下提供了一种方式来实现同步访问这就是lock

// Lock接口的源码
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
5.3 ReadWriteLock 接口

Synchronized的存在明显的问题就是读与读之间互斥,所以出现了ReadWriteLock接口

ReentrantReadWriteLock 是ReadWriteLock 的实现类

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}
5.4 ReentrantLock 类

ReentrantLock为常用类,它是一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

5.5 ReentrantReadWriteLock 类

ReentrantReadWriteLock是读写锁接口ReadWriteLock的实现类,它包括Lock子类ReadLock和WriteLock。ReadLock是共享锁,WriteLock是独占锁。

6. JUC 工具类

6.1 CountDownLatch 计数器

CountDownLatch为常用类,它是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

// 计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数是6,必须要执行任务的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" Go out");
                countDownLatch.countDown(); // 数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); // 等待计数器归零,然后再向下执行

        System.out.println("Close Door");
    }
}

countDownLatch.countDown(); // 数量-1

countDownLatch.await(); // 等待计数器归零,然后再向下执行

每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续

执行!

6.2 CyclicBarrier 加法计数器
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐7颗龙珠召唤神龙
         */
        // 召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(8,()->{
            System.out.println("召唤神龙成功!");
        });
        
        for (int i = 1; i <=7 ; i++) {
            final int temp = i;
            // lambda能操作到 i 吗
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
6.3 Semaphore:信号量
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量:停车位! 限流!
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <=6 ; 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 {
                    semaphore.release(); // release() 释放
                }

            },String.valueOf(i)).start();
        }
    }
}

原理:

semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止!semaphore.release(); 释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!

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

7. Collections 并发集合

image-20211207143953270

7.1 BlockingQueue 阻塞队列

BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。

先进先出。 写入:如果队列满了就必须阻塞等待 获取:如果队列是空的就必须阻塞等待生产

BlockingQueue

BlockingQueue 具有4组不同的方法用于插入、移除、以及对队列中的元素进行检查。

方式抛出异常有返回值,不抛出异常阻塞等待超时等待
添加addofferputoffer
移除removepolltakepoll
检测队首元素elementpeek-

例:

    public static void test1(){
        // 队列的大小
        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("=-===========");

        System.out.println(blockingQueue.element()); // 查看队首元素是谁
        System.out.println(blockingQueue.remove());

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

        // java.util.NoSuchElementException 抛出异常!
        // System.out.println(blockingQueue.remove());
    }

实现类:

ArrayBlockingQueue

LinkedBlockingQueue

ConcurrentLinkedQueue

7.2 Deque 双端阻塞队列

该队列同时支持FIFO和FILO两种操作方式

8. 线程池

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

image-20211207151448150

8.1 Executors 工具类
ExecutorService threadPool = Executors.newSingleThreadExecutor();		// 单个线 程// ExecutorService ExecutorService threadPool = Executors.newFixedThreadPool(5); 			// 创建一 个固定的线程池的大小 // ExecutorService threadPool = Executors.newCachedThreadPool(); 			// 可伸缩 的,遇强则强,遇弱则弱
8.2 ThreadPoolExecutor 类

4种拒绝策略

// ThreadPoolExecutor 构造器
// new ThreadPoolExecutor.AbortPolicy() 					// 银行满了,还有人进来,不处理这个人的,抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() 		  // 哪来的去哪里!
// new ThreadPoolExecutor.DiscardPolicy() 				// 队列满了,丢掉任务,不会抛出异常!
// new ThreadPoolExecutor.DiscardOldestPolicy()   // 队列满了,尝试去和最早的竞争,也不会抛出异常!
public ThreadPoolExecutor(int corePoolSize,											//核心线程池大小
                          int maximumPoolSize,									//最大线程池大小
                          long keepAliveTime,										//存活时间超时了没有人调用就会释放
                          TimeUnit unit,												//时间单位
                          BlockingQueue<Runnable> workQueue,		//阻塞队列
                          ThreadFactory threadFactory,					//线程工厂
                          RejectedExecutionHandler handler) {		//拒绝策略
  // ....
}
public class Demo01 {
    public static void main(String[] args) {
        // 最大线程到底该如何定义
        // 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高!
        // 2、IO  密集型   > 判断你程序中十分耗IO的线程,
        // 程序   15个大型任务  io十分占用资源!

        // 获取CPU的核数
        System.out.println(Runtime.getRuntime().availableProcessors());

        List list = new ArrayList();

        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());  //队列满了,尝试去和最早的竞争,也不会抛出异常!
        try {
            // 最大承载:Deque + max
            // 超过 RejectedExecutionException
            for (int i = 1; i <= 9; i++) {
                // 使用了线程池之后,使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

9. 四大函数式接口

函数式接口: 只有一个方法的接口 如Runable接口里面只有一个run方法

9.1 Function函数式接口
/**
 * Function 函数型接口, 有一个输入参数,有一个输出
 * 只要是 函数型接口 可以 用 lambda表达式简化
 */
public class FunctionDemo1 {
    public static void main(String[] args) {
//				传统方式
//        Function<String,String> function = new Function<String,String>() {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };

        // lambda表达式简化
        Function<String,String> function = str->{return str;};

        System.out.println(function.apply("asd"));
    }
}
9.2 Predicate断定式
/**
 * 断定型接口:有一个输入参数,返回值只能是 布尔值!
 */
public class Demo02 {
    public static void main(String[] args) {
        // 判断字符串是否为空
//        Predicate<String> predicate = new Predicate<String>(){
//            @Override
//            public boolean test(String str) {
//                return str.isEmpty();
//            }
//        };

        Predicate<String> predicate = (str)->{return str.isEmpty(); };
        System.out.println(predicate.test(""));

    }
}
9.3 Consumer 消费型接口
/**
 * Consumer 消费型接口: 只有输入,没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
        Consumer<String> consumer = (str)->{System.out.println(str);};
        consumer.accept("sdadasd");

    }
}
9.4 Supplier 供给型接口
/**
 * Supplier 供给型接口 没有参数,只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier<Integer>() {
//            @Override
//            public Integer get() {
//                System.out.println("get()");
//                return 1024;
//            }
//        };
        Supplier supplier = ()->{ return 1024; };
        System.out.println(supplier.get());
    }
}

10. CAS

CAS的全称是Compare-And-Swap,它是一条CPU并发原语。

正如它的名字一样,比较并交换,它是一种很重要的同步思想。如果主内存的值跟期望值一样,那么就进行修改,否则一直重试,直到一致为止。

CAS的方式为乐观锁,Synchronized为悲观锁。因此使用CAS解决并发问题通常情况下更优

cas大纲

10.1 CAS的ABA问题

所谓ABA问题,其实用最通俗易懂的话语来总结就是狸猫换太子

就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。

例:
比如有两个线程:
一开始都从主内存中拷贝了原值3;

A线程执行var5=this.getIntVolatile,即var5=3。此时A线程挂起;

B修改原为4,B线程执行完毕;

然后B线程觉得修改错了,然后又重新把值改为3;

A线程被唤醒,执行this.compareAndSwapInt()方法,发现这个时候主内存的值等于快照值3,(**但是却不知道B曾经修改过**),修改成功。

如何解决?

通过AtomicReference原子引用,就是加版本号。 本质上就是一个乐观锁的实现。

10.2 Unsafe 类

java不能直接访问操作系统,而是通过本地方法来调用。Unsafe类提供了硬件级别的原子操作,主要提供了一下操作

  1. 内存管理。包括分配内存、释放内存等。
  2. 非常规的对象实例化
  3. 操作类、对象、变量
  4. 数组操作
  5. 多线程同步
  6. 挂起与恢复。
  7. 内存屏障。
10.3 AtomicStampedReference 原子引用

解决ABA问题,引入原子引用! 跟乐观锁思想类似

public class CASDemo {
    //AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题

    // 正常在业务操作,这里面比较的都是一个个对象
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1, 1);

    // CAS  compareAndSet : 比较并交换!
    public static void main(String[] args) {

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>" + stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Lock lock = new ReentrantLock(true);

            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

            System.out.println("a2=>" + atomicStampedReference.getStamp());


            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a3=>" + atomicStampedReference.getStamp());

        }, "a").start();

        // 乐观锁的原理相同!
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>" + stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
            System.out.println("b2=>" + atomicStampedReference.getStamp());

        }, "b").start();

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值