JUC并发编程

JUC并发编程

1.进程和线程

进程:是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。

线程:是进程的一个实体,是CPU调度和分配的基本单位。

1.1 线程

创建线程的几种方式

  1. 继承Thread类创建线程
  2. 实现Runnable接口创建线程
  3. 通过Callable和Future适配器创建线程
  4. 通过线程池创键线程
//实现Runnable接口创建线程 lambda表达式 重写run()方法
new Thread(() -> {
            System.out.println("successful");
        },"线程A").start();

//通过Callable和Future适配器创建线程 重写call()方法
FutureTask<String> task = new FutureTask<>(() -> {
            System.out.println("callable");
            return "hh";
        });
        new Thread(task).start();
        System.out.println(task.get());
    }
//Runable 与 Callable的区别
/*1.Callable 有返回值
2.可以抛出异常
*/


1.2 Thread类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2BQgJX6p-1627480327886)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627450198762.png)]

Runnable 接口是唯一实现Thread方法的接口。

线程的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hp92oRj6-1627480327888)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627450533372.png)]

默认线程优先级NORM_PRIORITY = 5;

具有较高优先级的线程优先执行。

1.3 线程组

线程组管理线程,线程不能独立于线程组存在

 Thread thread = new Thread(new ThreadGroup("hello"), "A");
        ThreadGroup tg = thread.getThreadGroup();
        while (tg.getParent() != null) {
            tg = tg.getParent();
        }
        tg.list();

//结果
ThreadGroup采用树的结构管理着所有线程和线程组,顶层的是system,旗下有main组:
    java.lang.ThreadGroup[name=system,maxpri=10](maxpri优先级)
        Thread[Reference Handler,10,system] 引用处理
        Thread[Finalizer,8,system]          终结器
        Thread[Signal Dispatcher,9,system]  信号调度
        Thread[Attach Listener,5,system]    附加监听器
        java.lang.ThreadGroup[name=main,maxpri=10]  主线程组
            Thread[main,5,main]             主线程
            java.lang.ThreadGroup[name=hello,maxpri=10] 自定义hello线程组
                Thread[t8,5,hello]          hello组下的线程
1.4 重要方法
ClassLoader getContextClassLoader() 返回线程上下文的类加载器
    
getState() ;

getPriority() ;

isDaemon() ;

isAlive() ;

join() ;//插队 等到该线程结束

sleep(long millis) ;//线程睡眠 不释放锁

yield() ;//线程礼让,重新竞争,礼让不一定成功
    

1.5图解线程状态

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EyETRomk-1627480327889)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627453598781.png)]

1.5 sleep()方法和wait()方法的区别
  1. sleep()方法是主动让出CPU,不会释放锁,在sleep指定时间后继续执行,wait方法则是主动释放锁,只有在调用lnotify方法才会重新唤醒,或者在wait时间结束。
  2. sleep() 方法可以在任何地方使用,而 wait() 方法则只能在同步方法或同步块中使用;
  3. sleep() 是线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait() 是 Object 的方法,调用会放弃对象锁,进入等待队列,待调用 notify()/notifyAll() 唤醒指定的线程或者所有线程,才会进入锁池,再次获得对象锁才会进入运行状态 。
1.6 在 Java 程序中怎么保证多线程的运行安全

线程安全在三个方面体现:

原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);

可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized、volatile);

有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before 原则,volatile禁止指令重排)。

2.Lock

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lmYRMJpu-1627480327893)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627454577147.png)]

Lock相对于synchronized更加灵活,可以以任意顺序释放释放锁,从而使线程能够按照一定顺序执行。

2.1 公平锁与非公平锁

公平锁: 十分公平,必须先来后到~;

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

2.2 Synchronized 与Lock 的区别

1、Synchronized 内置的Java关键字,Lock是一个Java类

2、Synchronized 无法判断获取锁的状态,Lock可以判断

3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

2.3 虚假唤醒

如果使用if来判断可能会发生虚假唤醒,因此使用while

2.4 lock可以关联多个condition

精准的通知和唤醒线程

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTest {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(data::PrintA, "A").start();
        new Thread(data::PrintB, "B").start();
        new Thread(data::PrintC, "C").start();
    }
}

class Data {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1; //设置标志位

    public void PrintA() {
        lock.lock();
        try {
            while (num != 1) condition1.await();
            System.out.println(Thread.currentThread().getName() + "===>A" );
            num = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void PrintB() {
        lock.lock();
        try {
            while (num != 2) condition2.await();
            System.out.println(Thread.currentThread().getName() + "===>B" );
            num = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void PrintC() {
        lock.lock();
        try {
            while (num != 3) condition3.await();
            System.out.println(Thread.currentThread().getName() + "===>C" );
            num = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

2.5 锁的对象

明确锁的是什么

普通同步方法锁的是对象(最终锁的是方法的调用者,即对象实列);

静态同步方法锁的是class类,锁的是唯一对象模板。

3.集合不安全
3.1 List不安全

ArrayList在并发条件下是不安全的

  1. 可以使用Collections工具类下synchronized包装的list类

     List<Integer> list = Collections.synchronizedList(new ArrayList<>());
    
    
  2. Vector是线程安全的,但是底层使用Synchronized关键字来实现,效率低下,不推荐使用。

image-20200811144549151

  1. CopyOnWriteArrayList 写入时复制

CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

image-20200811144447781

3.2 set不安全
  • 使用Collections工具类的synchronized包装的Set类
  • 使用CopyOnWriteArraySet 写入复制的JUC解决方案
3.3 map不安全
  • ConcurrentHashMap 线程安全

  • HashTable线程安全

3.4 Java集合的快速失败机制

Java集合中有一个modCount变量,通过检测modCount变量是否为ExceptModCount来判断是否快速失败,抛出异常java.util.ConcurrentModificationException

4.常用的辅助类
4.1 CountDownLatch (倒计时锁)
  • A CountDownLatch是一种通用的同步工具,可用于多种用途。一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开。一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。
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");
    }
}

//主要方法
/*countDown() 减少锁存器的计数,如果计数达到零,释放所有等待的线程。 
*await() 导致当前线程等到锁存器计数到零,除非线程是 interrupted 。 
*/
4.2 CyclickBarrier (周期性屏障)

利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。下图演示了这一过程。

img

cyclicbarrier-2

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

public class CyclicBarrierTest {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(6, () -> {
            System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
            System.out.println("successful");
        });

        for (int i = 0; i < 18; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "get " + temp );
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

//运行结果
0get 0
1get 1
2get 2
3get 3
4get 4
5get 5
Thread.currentThread().getName() = 5
successful
6get 6
7get 7
8get 8
9get 9
10get 10
11get 11
Thread.currentThread().getName() = 11
successful
12get 12
13get 13
14get 14
15get 15
16get 16
17get 17
Thread.currentThread().getName() = 17
successful
    
//互相等待,到达预设的parties时,最后一个到达屏障的线程完成相应的动作
4.3 CountDownLatch 与 CyclickBarrier区别
  1. 在CyclicBarrier中线程调用await方法不仅会将自己阻塞还会将计数器减1,而在CountDownLatch中线程调用await方法只是将自己阻塞而不会减少计数器的值。
  2. CountDownLatch只能拦截一轮,而CyclicBarrier可以实现循环拦截
4.4 Semaphore(信号量)
public class SemaphoreDemo {
    public static void main(String[] args) {

        // 线程数量,停车位,限流
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; 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 (Exception e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release(); // release() 释放
                }
            }).start();
        }
    }
}

Thread-1抢到车位
Thread-0抢到车位
Thread-2抢到车位
Thread-0离开车位
Thread-2离开车位
Thread-1离开车位
Thread-5抢到车位
Thread-3抢到车位
Thread-4抢到车位
Thread-5离开车位
Thread-3离开车位
Thread-6抢到车位
Thread-4离开车位
Thread-6离开车位

Process finished with exit code 0

原理:

semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!

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

5.读写锁

使用ReadWriteLock 进行更细粒度的控制

ReadWriteLock用来维护一组相关联的lock

一次只允许一个线程修改,但是允许多个线程读取

package com.pt.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    public static void main(String[] args) {

        MyCash myCash = new MyCash();
        for (int i = 1; i < 6; i++) {
            final int temp = i;
            new Thread(() -> {
                myCash.write(temp, temp + 10);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i < 6; i++) {
            final int temp = i;
            new Thread(() -> {
                myCash.read(temp);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

class MyCash {
    private final Map<Integer, Integer> map = new HashMap<>();
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    Lock lock = new ReentrantLock();

    public void write(int k, int v) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + k);
            map.put(k, v);
            System.out.println(Thread.currentThread().getName() + "写入成功!");
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void read(int k) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + k);
            map.get(k);
            System.out.println(Thread.currentThread().getName() + "读取成功");
        } finally {
            readWriteLock.readLock().unlock();
        }

    }

    public void writeTest(int k, int v) {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + k);
            map.put(k, v);
            System.out.println(Thread.currentThread().getName() + "写入成功!");
        } finally {
            lock.unlock();
        }
    }

    public void readTest (int k) {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + k);
            map.get(k);
            System.out.println(Thread.currentThread().getName() + "读取成功");
        } finally {
            lock.unlock();
        }
    }
}



1写入1
1写入成功!
2写入2
2写入成功!
3写入3
3写入成功!
4写入4
4写入成功!
5写入5
5写入成功!
1读取1
2读取2
4读取4
1读取成功
5读取5
4读取成功
3读取3
3读取成功
2读取成功
5读取成功*

!理由上读写锁的性能优于互斥锁,但是如果读操作太短,由于读写锁本身比互斥锁复杂,可能导致性能比互斥锁差。

6.阻塞队列

image-20200812093115670

6.1阻塞队列的实用场景

多线程并发处理、线程池

6.2 BlockingQueue 有四组api
方式抛出异常不会抛出异常,有返回值阻塞,等待超时等待
添加addofferputoffer(timenum.timeUnit)
移出removepolltakepoll(timenum,timeUnit)
判断队首元素elementpeek--
6.3同步队列

同步队列 没有容量,也可以视为容量为1的队列;

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

put方法 和 take方法;

SynchronousQueue 和 其他的BlockingQueue 不一样 它不存储元素;

put了一个元素,就必须从里面先take出来,否则不能再put进去值!

并且SynchronousQueue 的take是使用了lock锁保证线程安全的。

7.线程池(重点)
7.1线程池的好处
  1. 降低资源的消耗;

  2. 提高响应的速度;

  3. 方便管理;

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

7.2线程池:三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
        ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
7.3七大参数
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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

image-20200812114142750

阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。

7.4拒绝策略
  1. new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常

超出最大承载,就会抛出异常:队列容量大小+maxPoolSize

  1. new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理

  2. new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。

  3. new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

7.5如何设置线程池的大小

1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小

// 获取cpu 的核数
        int max = Runtime.getRuntime().availableProcessors();
        ExecutorService service =new ThreadPoolExecutor(
                2,
                max,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

2、I/O密集型:

在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。

8.四大函数式接口
Function 函数型接口

image-20200812144105334

Predicate 断定型接口

image-20200812144545558

Suppier 供给型接口

image-20200812144653640

Consummer 消费型接口

image-20200812144803229

9.Stream流式计算
/**
 * Description:
 * 题目要求: 用一行代码实现
 * 1. Id 必须是偶数
 * 2.年龄必须大于23
 * 3. 用户名转为大写
 * 4. 用户名倒序
 * 5. 只能输出一个用户
 *
 * @author jiaoqianjin
 * Date: 2020/8/12 14:55
 **/

public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 23);
        User u2 = new User(2, "b", 23);
        User u3 = new User(3, "c", 23);
        User u4 = new User(6, "d", 24);
        User u5 = new User(4, "e", 25);

        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        // lambda、链式编程、函数式接口、流式计算
        list.stream()
                .filter(user -> {return user.getId()%2 == 0;})
                .filter(user -> {return user.getAge() > 23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((user1, user2) -> {return user2.compareTo(user1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

10.Fork/Join(分支合并)

image-20200812163638389

10.1ForkJoin 特点: 工作窃取!

实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!

image-20200812163701588

10.2如何使用ForkJoin?
  • 1、通过ForkJoinPool来执行

  • 2、计算任务 execute(ForkJoinTask task)

  • 3、计算类要去继承ForkJoinTask的实现子类recursiveTask,重写compute()方法;

    ForkJoin 的计算类

package com.pt.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

public class ForkJoinTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    public static void test1 () {
        long s = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 0; i <= 2000_000_000; i++) {
            sum += i;
        }
        long e = System.currentTimeMillis();
        System.out.println("sum = " + sum + " time = " + (e -s));
    }
    /*
    * 使用forkJoin框架*/
    public static void test2() throws ExecutionException, InterruptedException {
        long s = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> submit = forkJoinPool.submit(new ForkJoinDemo(0, 2000_000_000));
        Long sum = submit.get();
        long e = System.currentTimeMillis();
        System.out.println("sum = " + sum + " time = " + (e -s));
    }
    /*
    * 使用stream流计算*/
    public static void test3() {
        long s = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0, 2000_000_000).parallel().reduce(0, Long::sum);
        long e = System.currentTimeMillis();
        System.out.println("sum = " + sum + " time = " + (e -s));
    }
}

class ForkJoinDemo extends RecursiveTask<Long> {

    private long start;
    private long end;
    private long max = 1000_000L;

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

    @Override
    protected Long compute() {
        if (end - start < max) {
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            ForkJoinDemo fork1 = new ForkJoinDemo(start, (start + end) / 2);
            fork1.fork();
            ForkJoinDemo fork2 = new ForkJoinDemo((start + end) / 2 + 1, end);
            fork2.fork();
            return fork1.join() + fork2.join();
        }
    }
}

sum = 2000000001000000000 time = 731
sum = 2000000001000000000 time = 474
sum = 2000000001000000000 time = 371
10.3工作流程图

将大任务分支(fork)成小任务,递归拆分到任务足够小时计算,并合并(join)最后提交到ForkJoinTask。

这里写图片描述

10.4ForkjoinPool构造函数

ForkJoinPool有四个构造函数,其中参数最全的那个构造函数如下所示:

public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode)
  • parallelism:可并行级别,Fork/Join框架将依据这个并行级别的设定,决定框架内并行执行的线程数量。并行的每一个任务都会有一个线程进行处理,但是千万不要将这个属性理解成Fork/Join框架中最多存在的线程数量,也不要将这个属性和ThreadPoolExecutor线程池中的corePoolSize、maximumPoolSize属性进行比较,因为ForkJoinPool的组织结构和工作方式与后者完全不一样。而后续的讨论中,读者还可以发现Fork/Join框架中可存在的线程数量和这个参数值的关系并不是绝对的关联(有依据但并不全由它决定)。

  • factory:当Fork/Join框架创建一个新的线程时,同样会用到线程创建工厂。只不过这个线程工厂不再需要实现ThreadFactory接口,而是需要实现ForkJoinWorkerThreadFactory接口。后者是一个函数式接口,只需要实现一个名叫newThread的方法。在Fork/Join框架中有一个默认的ForkJoinWorkerThreadFactory接口实现:DefaultForkJoinWorkerThreadFactory。

  • handler:异常捕获处理器。当执行的任务中出现异常,并从任务中被抛出时,就会被handler捕获。

  • asyncMode:这个参数也非常重要,从字面意思来看是指的异步模式,它并不是说Fork/Join框架是采用同步模式还是采用异步模式工作。Fork/Join框架中为每一个独立工作的线程准备了对应的待执行任务队列,这个任务队列是使用数组进行组合的双向队列。即是说存在于队列中的待执行任务,即可以使用先进先出的工作模式,也可以使用后进先出的工作模式。

    这里写图片描述

    当asyncMode设置为ture的时候,队列采用先进先出方式工作;反之则是采用后进先出的方式工作,该值默认为false

10.5使用归并排序解决排序问题
package com.pt.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

public class ForkJoinTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    public static void test1 () {
        long s = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 0; i <= 2000_000_000; i++) {
            sum += i;
        }
        long e = System.currentTimeMillis();
        System.out.println("sum = " + sum + " time = " + (e -s));
    }
    /*
    * 使用forkJoin框架*/
    public static void test2() throws ExecutionException, InterruptedException {
        long s = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> submit = forkJoinPool.submit(new ForkJoinDemo(0, 2000_000_000));
        Long sum = submit.get();
        long e = System.currentTimeMillis();
        System.out.println("sum = " + sum + " time = " + (e -s));
    }
    /*
    * 使用stream流计算*/
    public static void test3() {
        long s = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0, 2000_000_000).parallel().reduce(0, Long::sum);
        long e = System.currentTimeMillis();
        System.out.println("sum = " + sum + " time = " + (e -s));
    }
}

class ForkJoinDemo extends RecursiveTask<Long> {

    private long start;
    private long end;
    private long max = 1000_000L;

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

    @Override
    protected Long compute() {
        if (end - start < max) {
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            ForkJoinDemo fork1 = new ForkJoinDemo(start, (start + end) / 2);
            fork1.fork();
            ForkJoinDemo fork2 = new ForkJoinDemo((start + end) / 2 + 1, end);
            fork2.fork();
            return fork1.join() + fork2.join();
        }
    }
}

11.异步回调

image-20200812215150294

但是我们平时都使用CompletableFuture

(1)没有返回值的runAsync异步回调
public static void main(String[] args) throws ExecutionException, InterruptedException 
{
        // 发起 一个 请求

        System.out.println(System.currentTimeMillis());
        System.out.println("---------------------");
        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
            //发起一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+".....");
        });
        System.out.println(System.currentTimeMillis());
        System.out.println("------------------------------");
        //输出执行结果
        System.out.println(future.get());  //获取执行结果
 }

(2)有返回值的异步回调supplyAsync
//有返回值的异步回调
CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{
    System.out.println(Thread.currentThread().getName());
    try {
        TimeUnit.SECONDS.sleep(2);
        int i=1/0;
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
    //success 回调
    System.out.println("t=>" + t); //正常的返回结果
    System.out.println("u=>" + u); //抛出异常的 错误信息
}).exceptionally((e) -> {
    //error回调
    System.out.println(e.getMessage());
    return 404;
}).get());

whenComplete: 有两个参数,一个是t 一个是u

T:是代表的 正常返回的结果

U:是代表的 抛出异常的错误信息

如果发生了异常,get可以获取到exceptionally返回的值;

12.JMM(Java memory mode)
12.1对Volatile 的理解

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

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

12.2什么是JMM?

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

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

1、线程解锁前,必须把共享变量立刻刷回主存;

2、线程加锁前,必须读取主存中的最新值到工作内存中;

3、加锁和解锁是同一把锁;

线程中分为 工作内存、主内存

8种操作:

Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;

load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;

Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;

assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;

store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;

write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;

lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;

unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

image-20200812215247240

JMM对这8种操作给了相应的规定:

不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存

image-20200812215606080

遇到问题:程序不知道主存中的值已经被修改过了!; 通过volatile来保证可见性

13.单例模式
13.1 饿汉式
13.2 懒汉式
13.3静态内部类
13.4枚举
14.CAS

比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Unsafe 类

image-20200812220347822

image-20200812220411463

缺点:

  • 循环会耗时;
  • 一次性只能保证一个共享变量的原子性;
  • 它会存在ABA问题
ABA问题

image-20200812220441615

线程1:期望值是1,要变成2;

线程2:两个操作:

  • 1、期望值是1,变成3
  • 2、期望是3,变成1

所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;

带版本号的原子操作

package com.marchsoft.lockdemo;

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

/**
 * Description:
 *
 * @author jiaoqianjin
 * Date: 2020/8/12 22:07
 **/

public class CASDemo {
    /**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
     * 正常在业务操作,这里面比较的都是一个个对象
     */
    static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(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();
            }
            // 修改操作时,版本号更新 + 1
            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            
            System.out.println("a2=>" + atomicStampedReference.getStamp());
            // 重新把值改回去, 版本号更新 + 1
            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, 3,
                    stamp, stamp + 1));
            System.out.println("b2=>" + atomicStampedReference.getStamp());
        }, "b").start();
    }
}


15.各种锁的理解
15.1公平锁,非公平锁
  1. 公平锁:非常公平,不能插队,必须先来后到
  2. 非公平锁:非常不公平,允许插队,可以改变顺序
15.2可重入锁
image-20200812213957137
15.3自旋锁
15.4死锁

死锁的检测

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

image-20200812214833647

2、使用jstack 进程进程号 找到死锁信息

image-20200812214920583

一般情况信息在最后:

image-20200812214957930

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, 3,
                stamp, stamp + 1));
        System.out.println("b2=>" + atomicStampedReference.getStamp());
    }, "b").start();
}

}


#### 15.各种锁的理解

##### 15.1公平锁,非公平锁

1. 公平锁:非常公平,不能插队,必须先来后到
2. 非公平锁:非常不公平,允许插队,可以改变顺序

####  15.2可重入锁

#### ![image-20200812213957137](https://i-blog.csdnimg.cn/blog_migrate/96c04760e13e393233639ce126b09b9a.png)

#### 15.3自旋锁

#### 15.4死锁

死锁的检测

**1、使用jps定位进程号,jdk的bin目录下: 有一个jps**

命令:`jps -l`

![image-20200812214833647](https://i-blog.csdnimg.cn/blog_migrate/7bee08d3b5ace01c24286b19465c9ce3.png)

**2、使用`jstack` 进程进程号 找到死锁信息**

![image-20200812214920583](https://i-blog.csdnimg.cn/blog_migrate/2e1df30c553f3c9fb255feec7ec46247.png)

**一般情况信息在最后:**

![image-20200812214957930](https://i-blog.csdnimg.cn/blog_migrate/fa64cab8ddb7fa7c9f426dd9401580ce.png) 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值