JUC基础(周阳老师笔记

一、JMM

1.volatile

volatile是java虚拟机提供的轻量级的同步机制(青春版synchronized)

可以保证可见性、有序性(指令重排序),但不能保证原子性

主内存是线程共享的。

JVM会从主内存中拷贝变量到线程中,即工作变量,工作变量是线程私有的。

volatile关键字的变量会在工作变量改变后,通知其他线程丢弃其拷贝重新在主内存中拷贝一份到自己的工作空间。

2.加载代码练习:

加载顺序:静态代码块>普通代码块>构造方法

静态代码直接放进方法区,第二次就不再需要加载了,直接指向方法区


二、JUC基础

  • 口诀:
  1. 高内聚低耦合前提下,线程操作资源类
  2. 判断/干活/通知
  3. 多线程交互中要防止多线程的虚假唤醒(判断只用while,不用if)
  4. 标志位

1.什么是进程/线程,并发/并行

进程/线程

进程就是idea,线程就是idea中的

详细一点进程就是操作系统分配资源的基本单位,线程比进程笑,是独立运行和调度的基本单位

并发/并行

并发就是同一时刻操作同一资源

并行就是同一时间操作同一资源(CPU在极短的时间段内完成时间片轮转)

2.线程的状态

public enum State {
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}

new Thread().start()之后不是立马运行,只是就绪状态,要等待操作系统的cpu调度

3.线程 操作 资源类

操作就是对外暴露的调用方法。

  1. 创建资源类,不要去继承Thread类或者实现Runnable接口,就让他是一个纯净的资源类
  2. 用synchronized标识共享资源(旧) /

用ReentrantLock(可重入锁)来锁局部代码块(新),从而缩小我锁的粒度范围,不要一竿子打死,这样我的并发性就好、高

资源类中要:private Lock lock = new ReentrantLock();

ReentrantLock像公厕坑位的锁,synchronized就是公厕大门的锁

(学会用idea的live template来抽取代码为自定义快捷输入)(语法糖)

4.Lambda表达式

  • 口诀:拷贝小括号,写死右箭头,落地大括号

匿名内部类代码太长,所以要用lambda表达式简化代码

如果要用lambda表达式,接口必须是函数式接口(functional interface)

可以直接在接口上加@FuctionalInterface注解帮助校验,不加的话java也会自己帮我们加

jdk8以后的interface

可以有部分带实现的方法 default

可以有静态方法(接口名直接调用)

5.判断/干活/通知

这里判断直接上while是为了方便能够运行,教程中先是if为了引出虚假唤醒的问题。

老版写法(synchronized):

class Bakery{
    private int cake = 0;
    public synchronized void increment() throws InterruptedException {
        while(cake!=0){
            this.wait();
        }
        cake++;
        System.out.println(Thread.currentThread().getName()+"\t 数量:"+cake);
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        while(cake==0){
            this.wait();
        }
        cake--;
        System.out.println(Thread.currentThread().getName()+"\t 数量:"+cake);
        this.notifyAll();
    }

}

public class ProducerAndConsumer {
    public static void main(String[] args) {
        Bakery bakery = new Bakery();
        new Thread(()->{
            try {
                for(int i =1;i<=10;i++) bakery.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"蛋糕师1").start();
        new Thread(()->{
            try {
                for(int i =1;i<=10;i++) bakery.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"蛋糕师2").start();
        new Thread(()->{
            try {
                for(int i =1;i<=10;i++) bakery.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者1").start();
        new Thread(()->{
            try {
                for(int i =1;i<=10;i++) bakery.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者2").start();
    }
}

新版本(ReentrantLock版本): (main方法就不写了)

lock配合condition的await和signal 对应synchronized配合object的wait和notify

class Bakery {
    private int cake = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (cake != 0) {
                condition.await();
            }
            cake++;
            System.out.println(Thread.currentThread().getName() + "\t 数量:" + cake);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (cake == 0) {
                condition.await();
            }
            cake--;
            System.out.println(Thread.currentThread().getName() + "\t 数量:" + cake);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

}

6.防止虚假唤醒(while not if)

虚假唤醒的原因:

可能因为多个进程进入if后被wait,而if只判断一次,然后因为某个线程的notifyAll后再执行下去,导致连续多次++或—

解决:把原来的if换成while,让他们即使被唤醒了,仍要重新判断

7.标志位

使用标志位可以实现精准通知(我做完了你做,实现多线程之间的顺序调用)

lock精准通知

lock配合condition的await和signal 对应synchronized配合object的wait和notify

class SharedResource {
    private int sharedreference = 1;
    private Lock lock = new ReentrantLock();
    //condition1 != condition2 != condition3
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    private int num = 1;

    public void share01() throws InterruptedException {
        lock.lock();
        try {
            while (num != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "\t进入");
            num = 2;
            condition2.signal();
        } finally {
            lock.unlock();
        }
    }

    public void share02() throws InterruptedException {
        lock.lock();
        try {
            while (num != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "\t进入");
            num = 3;
            condition3.signal();
        } finally {
            lock.unlock();
        }
    }

    public void share03() throws InterruptedException {
        lock.lock();
        try {
            while (num != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "\t进入");
            num = 1;
            condition1.signal();
        } finally {
            lock.unlock();
        }
    }

}

public class SequenceSignal {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();
        new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) sharedResource.share01();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread 1").start();
        new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) sharedResource.share02();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread 2").start();
        new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) sharedResource.share03();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread 3").start();
    }
}

condition

为了搞清楚精准通知的原理,应该先要弄懂condition是到底是个什么东西

一个Condition实例本质上绑定到一个锁

一定要看下面这个链接,这会马上备考概率论了所以没看

https://segmentfault.com/a/1190000037693391

8.多线程八锁

class Phone {
    public synchronized void sendEmail() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("============sendEmail");
    }

    public  synchronized void sendSMS() {
        System.out.println("============sendSMS");
    }

    public void hello(){
        System.out.println("======HelloWorld");
    }
}

public class EightLock {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }, "A").start();
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}
邮件在先,短信在后

1 标准访问,先打印短信还是邮件-----------------------------------邮件
2 停4秒在邮件方法内,先打印短信还是邮件---------------------------邮件
3 普通的hello方法,是先打邮件还是hello--------------------------hello
4 现在有两部手机,先打印短信还是邮件-----------------------------短信
5 两个静态同步方法,1部手机,先打印短信还是邮件-------------------邮件
6 两个静态同步方法,2部手机,先打印短信还是邮件-------------------邮件
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件----短信
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件----短信

main线程里写的线程代码先后执行顺序还真不一定 ,需要等待操作系统对线程优先级排序 阻塞的数字啥的

TimeUnit.SECONDS.sleep()

原因:(前提得锁的对象是一致才能发挥锁的作用)

1、2:synchronized锁的是当前对象this,即phone1

3:锁的东西不一样,普通方法不会去抢,各不相干

4:锁的不一样对象(2部手机),因为邮件要挂2s,所以先短信

5、6:synchronized修饰静态方法锁的是整个类,即class,而不是对象

7、8:锁的不是一个东西


三、JUC集合类

1.List

请举例说明List不安全:(add没有synchronized修饰)

报:java.util.ConcurrentModificationException

  • ArrayList、StringBuilder、CopyOnWriteArrayList底层都用到了Arrays.copyOf
  • Arrays.copyOf方法返回原始数组的副本,截断或填充空值以获得指定的长度。

ArrayList:

在这里插入图片描述

CopyOnWriteArrayList:

CopyOnWrite即写时复制的容器。往容器中添加元素的时候,不直接往当前容器添加,而是先复制出一个新的容器,往新的容器中添加元素。添加完成后再将原容器的引用指向新容器setArray(newElements);。这样就能并发的读而不需要加锁,这是一种读写分离的思想,读和写不同的容器。

在这里插入图片描述

StringBuilder:
在这里插入图片描述

解决:

ArrayList<Object> objects = new ArrayList<>();//线程不安全
Vector<Object> objects2 = new Vector<>(); //线程安全,全是synchronized修饰,基本不用,性能太差
Collections.synchronizedList(objects);//转化为线程安全
CopyOnWriteArrayList<Object> objects1 = new CopyOnWriteArrayList<>();//线程安全,写时复制

2.set

hashset的底层是hashmap

hashset的add调的是hashmap的put value是PRESENT的常量

concurrenthashmap copyonwriteset 线程安全(没有深入,最好深入一下)

https://segmentfault.com/a/1190000021144667 性能对比

又带着复习了一遍hashmap,数组+链表+红黑树

负载因子*当前容量=threshold 门槛


四、Callable接口

  • 与继承thread类、实现runnable接口相比,实现callable接口有返回值、有异常,重写方法不一样。

因为Thread的构造方法中只提供了runnable接口,没有callable接口

所以根据多态的性质,我们需要一个同时实现了runnable和callable接口的类,即futuretask类

FutureTask,实现了runnable接口,构造方法可以传入callable接口

在这里插入图片描述

在这里插入图片描述

然后在thread中传入futuretask,具体代码如下

class Mythread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("******进入future方法");
        TimeUnit.SECONDS.sleep(2);
        return 123;
    }
}
public class MyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Mythread mythread = new Mythread();
        FutureTask<Integer> integerFutureTask = new FutureTask<>(mythread);
        for(int i=1;i<=10;i++){
            new Thread(integerFutureTask,String.valueOf(i)).start();
        }
        System.out.println("计算完成");
        System.out.println(integerFutureTask.get());//get不到会阻塞主线程,所以get一般放在最后
    }
}

注意细节:

get不到会阻塞主线程,所以get一般放在最后

一个futuretask 多个线程调用只会调用一次,内部有缓存。

futuretask.get 帮忙计算买水的例子


五、强大的辅助类

1.countDownLatch

上晚自习班长关门的例子

countDownLatch : 计数器

CountDownLatch countDownLatch = new CountDownLatch(5);
for(int i=1;i<=5;i++)
new Thread(()->{
    System.out.println(Thread.currentThread().getName()+"走出了教室");
    countDownLatch.countDown();
        },String.valueOf(i)).start();

countDownLatch.await();
System.out.println("========班长关门了");

CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。

其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),

当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

2.CyclicBarrier

CyclicBarrier : 到齐开会,集齐龙珠

CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            System.out.println("开始开会");
        });
for (int i = 1; i <= 5; i++) {
    new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + "进入了会议室");
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }, String.valueOf(i)).start();
}=

CyclicBarrier的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

线程进入屏障通过CyclicBarrier的await()方法

3.Semaphore

信号量机制:争车位。

信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

Semaphore semaphore = new Semaphore(2);
for (int i = 1; i <= 5; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "进入了停车场");
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "离开了停车场");
            semaphore.release();
        }

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

也能利用信号量机制实现synchronized实现不到的效果,如让一个线程每次拿到10s锁就释放


六、ReentrantReadWriteLock

读写锁。

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReentrantReadWriteLock readwriteLock=new ReentrantReadWriteLock();
    public void put(String key, Object value) {
        readwriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "\t 正在写" + key);
        //暂停一会儿线程
        try {
            //TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t 写完了" + key);
        readwriteLock.writeLock().unlock();
    }

    public Object get(String key) {
        readwriteLock.readLock().lock();
        Object result = null;
        System.out.println(Thread.currentThread().getName() + "\t 正在读" + key);
        try {
            TimeUnit.SECONDS.sleep(2);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读完了" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
           readwriteLock.readLock().unlock();
        }
        return result;//return 前会执行finally
    }
}

public class ReadwriteLock {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(String.valueOf(num));
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(String.valueOf(num), String.valueOf(num));
            }, String.valueOf(i)).start();
        }
    }
}

七、阻塞队列

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

add throwing an IllegalStateException if this queue is full

offer returning true upon success and false if this queue is full. This method is generally preferable to method add(E)

put 阻塞无返回值

offer(e,time,unit) 带返回值,阻塞超时退出 (这个好诶)


八、线程池

银行窗口,办理业务,请xx到x号窗口办理业务

1.线程池的优点

就像数据库连接池一样,我们频繁创建销毁线程需要消耗资源,于是用线程池来控制运行的线程数量。

处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果任务数量超过了最大线程数量,超出数量的任务排队等候(阻塞队列),等其他线程执行完毕,再从队列中取出任务来执行。

它的主要特点为:线程复用;控制最大并发数;管理线程。

2.线程池的创建

主要是通过Executor框架实现的,核心是ThreadPoolExecutor类,我们自定义也是用这个类
在这里插入图片描述

Executors工具类的三种预设方案(实际开发中一般不用)

在这里插入图片描述

3.七大参数

在这里插入图片描述

在这里插入图片描述

corePoolSize:核心线程数

maximumPoolSize:池子的最大线程数

keepAliveTime:非核心线程数空闲持续时间

unit:时间单位

workQueue:阻塞队列

threadFactory:线程创建的工厂,一般为默认

handler:拒绝策略

4.线程池的底层工作原理

先核心线程工作,如果繁忙,任务进阻塞队列

如果阻塞队列满了,就会扩容到池子最大线程数

如果还是让阻塞队列满了,就会进行拒绝策略

然后当一个线程无事可做超过keepAliveTime时间,那么如果当前线程数量大于corePoolSize,这个线程就会被停掉。所以线程池的所有任务完成后,最终收缩到corePoolSize的大小

阿里巴巴开发手册不让用三个预设(Executors),自定义自己写,这样也清晰(七大参数)

在这里插入图片描述

5.自定义线程池:ThreadPoolExecutor核心类

threadPool.execute(()→{}); 线程池执行

threadPool.shutdown(); 线程池关闭

在这里插入图片描述

public static void main(String[] args) {
    ExecutorService myThreadPool = new ThreadPoolExecutor(
            2,  //corePoolSie
            5, //maximumPoolSize
            2L,  //keepAliveTime
            TimeUnit.SECONDS, //unit
            new ArrayBlockingQueue<>(3), //workQueue
            Executors.defaultThreadFactory(), //threadFactory
            new ThreadPoolExecutor.AbortPolicy()   //handler 【默认】
            //new ThreadPoolExecutor.CallerRunsPolicy() //handler
            //new ThreadPoolExecutor.CallerRunsPolicy() //handler
            //new ThreadPoolExecutor.DiscardPolicy() //handler
    );
    try {
        //10个顾客请求
        for (int i = 1; i <= 10; i++) { //最大容纳数是:maximumPoolSize+workQueue 5+3=8
            myThreadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 办理业务");
            });
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        myThreadPool.shutdown();
    }
}

6.确定线程池的大小

不写死,Runtime.getRuntime().availableProcessors(); 获得cpu核数

根据具体业务设定最大线程数:

  • CPU密集型:大量计算,cpu 占用越接近 100%;Nthreads = Ncpu+1
  • IO密集型:大量网络,文件操作;Nthreads = Ncpu x Ucpu x (1 + W/C),

详见《Java并发编程实践》

  • Ncpu = CPU的数量
  • Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
  • W/C = 等待时间与计算时间的比率

九、Java8新特性

1.四大函数式接口

使用 @FunctionalInterface 标识,有且仅有一个抽象方法,可被隐式转换为 lambda 表达式

在这里插入图片描述

如果代码量只有一行,可以适当简化lambda表达式

在这里插入图片描述

2.Stream流式计算

  • 集合讲的是数据,流讲的是计算
  • 注意:Stream流式计算 和 IO文件读写流 两者完全不是一回事

特点:

  • Stream自己不会存储数据,不会改变源对象,他们会返回一个持有结果的新Stream
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 类:引入函数式编程风格,提供了很多功能,使代码更加简洁。方法包括 forEach 遍历、count 统计个数、filter 按条件过滤、limit 取前 n 个元素、skip 跳过前 n 个元素、map 映射加工、concat 合并 stream 流等。

这一些列方法需要传入四大函数式接口,传入的参数是stream流。

如:

User u1 = new User(1,"a",10);
User u2 = new User(2,"b",17);
User u3 = new User(3,"c",20);
User u4 = new User(4,"d",28);
User u5 = new User(5,"e",30);
List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
users.stream().filter(p -> {
  return p.getId() % 2 == 0;
}).filter(p -> {
  return p.getAge() > 24;
}).map(f -> {
  return f.getUserName().toUpperCase();
}).sorted((o1, o2) -> {
  return o2.compareTo(o1);
}).limit(1).forEach(System.out::println);

十、其他类

1.分支合并框架

Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并

主要还是分治的思想,只不过这里是多个线程分治

相关类

  • ForkJoinPool:分支合并池(类似线程池)
  • ForkJoinTask:(类似FutureTask的构造方法接callable),这里要用Pool的submit方法
  • RecursiveTask:抽象类继承自ForkJoinTask,用来被继承,重写compute方法;这里作多线程的递归

在这里插入图片描述

具体使用代码

class MyTask extends RecursiveTask<Integer> {
    private static final Integer ADJUST_VALUE = 10;
    private int begin;
    private int end;
    private int result;
    public MyTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }
    @Override
    protected Integer compute() {
        if ((end - begin) <= ADJUST_VALUE) { //10以内太小了就没必要拆分了
            for (int i = begin; i <= end; i++) {
                result = result + i;
            }
        } else {
            int middle = (begin + end) / 2;
            MyTask task01 = new MyTask(begin, middle);
            MyTask task02 = new MyTask(middle + 1, end);
            task01.fork();
            task02.fork();
            result = task01.join() + task02.join();
        }
        return result;
    }
}

public class ForkJoinDemo {
    public static void main(String[] args) throws Exception {
        MyTask myTask = new MyTask(0, 100); //继承了RecursiveTask,实现0~100的相加
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask); //分支合并池提交Task
        System.out.println(forkJoinTask.get());
        forkJoinPool.shutdown();
    }
}

2.异步回调

public static void main(String[] args) throws Exception {
    CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{ //Void是void包装类
        System.out.println(Thread.currentThread().getName()+"没有返回值,update mysql ok");
    });
    completableFuture1.get();

    //------------------------------------------------------------------------------------------
    CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
        System.out.println(Thread.currentThread().getName()+"有返回值,insert mysql ok");
        int i = 1/0;
        return 1024;
    });
    System.out.println(completableFuture2.whenComplete((t, u) -> {  // BiConsumer<? super T, ? super Throwable> action 要有两个输入参数,且没有返回值
        System.out.println("t=" + t); //t:正确完成
        System.out.println("u=" + u); //u:异常
    }).exceptionally(f -> {   //如果异常,则走这里
        System.out.println("exception:" + f.getMessage());
        return 404;
    }).get());

}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值