JUC 并发编程进阶

JUC 进阶

1. JUC主要的四个包

  1. java.util.concurrent
  2. java.util.concurrent.atomic
  3. java.util.concurrent.locks
  4. java.util.concurrent.function

2. 线程和进程

  1. 进程是程序的一次执行过程;
  2. 线程是比进程更小的执行单位,一个进程可以有多个线程,线程也称为轻量级进程;
  3. 进程间一般相互独立,同类线程之间可以共享资源;
  4. Java默认有两个线程:main线程和gc线程;

并发和并行

  1. 并发是交替执行;
  2. 并行是一起执行。

线程的状态

  1. New
  2. Runnable:- Ready, - Running
  3. Blocked
  4. Waiting
  5. Timed_Waiting
  6. Terminated

wait()和sleep()的区别

  1. 属于不同的类:wait属于Object类,sleep属于Thread类;
  2. 二者都可以暂停线程的运行;
  3. sleep不释放锁,wait释放锁;
  4. sleep自动苏醒,wait需要被其他线程唤醒;
  5. sleep主要用于线程的暂停,wait用于线程间的交互通信;

3. Lock锁

公平锁,非公平锁

  1. 公平锁:十分公平,先来后到
public ReentrantLock(boolean fair){
    sync = fair ? new FairSync() : new NonfairSync();
}
  1. 非公平锁 (默认):不公平,可以插队
public Reentrantlock(){
    sync = new NonfairSync();
}

Lock锁三部曲

  1. 创建锁:new ReentrantLock();
  2. 上锁:lock();
  3. 解锁:unlock()

Synchronized 和Lock的区别

  1. Syn是关键字,Lock是一个类;
  2. Syn无法判断锁的状态,Lock可以判断是否获取到了锁;
  3. Syn自动释放锁,Lock手动释放锁;
  4. Syn是非公平锁,Lock可以自己设置;
  5. Syn适合锁少量的同步代码,Lock适合锁大量的代码。

4. 生产者消费者模式

链接点击跳转

  1. 注意使用while()避免虚假唤醒问题;

  2. 使用comdition可实现精准唤醒:condition1.signal(), condition2.signal();

5. 八锁问题分析

链接点击跳转

6. 集合不安全类

  1. ArrayList不安全, 解决方案:
List<String> list = new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
  1. HashSet不安全:
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
  1. HashMap不安全:
Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
Map<String, String> map = new ConcurrentHashMap<>();

7. Callable

Runnable和Callable接口的区别

Callable有返回值,会抛出异常,Runnable并不会;

如何启动Callable?

找到关系

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        myThread myThread = new myThread();
        FutureTask futureTask = new FutureTask(myThread);	//适配类
        new Thread(futureTask, "xianchengName").start();

        Object o = futureTask.get();    //获取callable返回的结果
        System.out.println(o);
    }
}
class myThread implements Callable {
    @Override
    public Object call() throws Exception {
        return null;
    }
}

execute()和submit()的区别

  1. execute()只能提交Runnable类型的任务,而submit()可以提交Runnable和Callable类型的任务;
  2. execute()提交不需要返回值的任务,submit()用于提交需要返回值的任务。

8. 常用辅助类

  1. CountDownLatch

原理:每次有线程调用countDown()就-1,当计数器变为0,await()就会被唤醒,继续向下执行;

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

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

作用与countDownLatch相似;

  1. Semaphore

可以指定多个线程同时访问某一资源;

并发限流;

控制最大线程数;

9. 读写锁 ReadWriteLock

更加细粒度的锁;

ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//写锁,某一时刻只允许一个线程写
reentrantReadWriteLock.writeLock().lock();
reentrantReadWriteLock.writeLock().unlock();
//读锁,某一时刻允许多个线程读
reentrantReadWriteLock.readLock().lock();
reentrantReadWriteLock.readLock().unlock();

10. 阻塞队列

什么时候使用阻塞队列

线程池里的 workqueue

四组API

方式抛出异常有返回值,不抛异常阻塞等待超时等待
添加add()offer()put()offer( , , )
删除remove()poll()take()poll( , )
检测队首元素element()peek()

11. 线程池

三大方法(已不建议使用)建议使用 new ThreadPoolExecutor()

ExecutorService single = Executors.newSingleThreadExecutor();   //创建单个线程
ExecutorService fixed = Executors.newFixedThreadPool(5);    //创建固定大小线程池
ExecutorService cache = Executors.newCachedThreadPool();    //创建可伸缩大小的线程池

七大参数

 public ThreadPoolExecutor(int corePoolSize,	//核心线程数
                              int maximumPoolSize,	//最大线程数
                              long keepAliveTime,	//超时没调用者释放时间
                              TimeUnit unit,	//时间单位
                              BlockingQueue<Runnable> workQueue,	//阻塞队列
                              ThreadFactory threadFactory,	//线程工厂,一般不动
                              RejectedExecutionHandler handler) {	//拒绝策略,四种

四种拒绝策略

new ThreadPoolExecutor.AbortPolicy();	//不处理,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy();	//哪里来去哪里
new ThreadPoolExecutor.DiscardPolicy();	//丢弃不处理,不抛异常
new ThreadPoolExecutor.DiscardOldestPolicy();	//尝试和最早的竞争,不抛异常

最大线程数如何设置?

  1. CPU密集型:CPU是几核,就等于几,可保持CPU的效率最高;
  2. IO密集型:一般是程序中十分耗费IO资源线程数的两倍;

12. 四大函数式接口

函数式接口:只有一个函数的接口,如 Runnable;

  1. 函数型接口:两个参数,一个输入参数,一个输出参数
public interface Function<T, R>{
    R apply(T t);
}
  1. 断定型接口:只有一个输入参数,返回值为boolean型
public interface Predicate<T>{
    boolean test(T t);
}
  1. 消费型接口:只有一个输入参数
public interface Consumer<T>{
    void accpet(T t);
}
  1. 供给型接口:只有一个返回值
public interface Supplier<T>{
    T get();
}

13. Stream 流式计算

函数式编程(Stream流式计算是主要内容)

不关心具体对象是谁,只关心数据是什么,要做什么操作。
优点: 1. 代码简洁;2. 有利于并发编程;3. 更接近于自然语言,易于理解;

import java.util.*;
public class Main {
    /**
     * Java 函数式编程(主要是stream流式计算):不关心具体对象是谁,方法名是什么,只关心数据是什么以及如何操作数据;
     * 优点:1. 代码简洁;
     *      2. 有利于并发编程;
     *      3. 更接近于自然语言,易于理解;
     * 示例: 题目要求:现有5个用户,筛选:
     *      1. ID必须是偶数
     *      2. 年龄必须大于23
     *      3. 用户名转为大写字母
     *      4. 用户名字母倒着排序
     *      5. 只输出一个用户;
     */
    public static void main(String[] args) {
        User a = new User(1, "aaa", 21);
        User b = new User(2, "bbb", 22);
        User c = new User(3, "ccc", 23);
        User d = new User(4, "ddd", 24);
        User e = new User(5, "eee", 25);
        List<User> users = Arrays.asList(a, b, c, d, e);
        users.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);
    }
}
//User类
class User{
    int id;
    String name;
    int age;
    public User(int id, String name, int age){
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public int getId() {return id;}
    public void setId(int id) {this.id = id;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
    @Override
    public String toString() {return "User{" + "id=" + id +", name=" + name +", age=" + age + "}";}
}

14. ForkJoin

分支计算

15. 异步回调

16. JMM

线程可以将变量保存到本地内存,而不是直接在主内存中进行读写;这样,可能造成一个线程在主内存中修改了变量的值,而另一个线程还使用着这个变量在本地内存的拷贝,可能会造成数据不一致。使用volatile关键字标识这个变量是不稳定的,每次都要在主内存中进行读取。

在这里插入图片描述

八种内存交互操作(必须成对出现):

  1. lock(),
  2. unlock(),
  3. read(),
  4. load(),
  5. use(),
  6. assign(),
  7. write(),
  8. store()

17. Volatile

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

不使用lock和synchronized如何保证原子性?

答:使用原子类java.util.concurrent.atomic

import java.util.concurrent.atomic;

private volatile static AtomicInteger atomicInteger = new AtomicInteger();

Volatile 和Synchronized的区别

  1. volatile是线程同步的轻量级实现,性能要比Syn好,但volatile只能修饰变量,而Syn能修饰方法和代码块;
  2. volatile可以保证可见性,但不能保证原子性,Syn都可以保证;
  3. volatile更多是用于解决线程间变量的可见性问题,而Syn更多用于线程间访问资源同步性问题。

18. 单例模式

链接点击跳转

19. 深入理解CAS

CAS:比较并交换,Java可以通过CAS保证原子性;

当多个线程同时操作一个变量时,只有一个线程操作成功,其他线程失败;但失败并不是挂起,而是继续尝试,也就是自旋;Java自旋锁就是利用CAS实现的。

AtomicInteger atomicInteger = new AtomicInteger();
        //达到期望值就更新,否则就不更新
        boolean b = atomicInteger.compareAndSet(1, 2);

ABA问题

在CAS的算法流程中,首先要比较更新值和期望值(excepted)是否相同,相同则更新;

ABA问题是指:期望值旧值是A,被更新成B,后来又被更新为A,则另一个线程来更新,发现依然是A就直接修改了,这样是错误的。

解决方法:利用版本号;每次变量更新的时候,版本号+1。

20. 原子引用

AtomicReference<Object> objectAtomicReference = new AtomicReference<>();

带标记的原子引用来解决ABA问题

AtomicStampedReference<Object> objectAtomicStampedReference = new AtomicStampedReference<>();

21. 各种锁的理解

  1. 公平锁,非公平锁:是否可以插队;
  2. 可重入锁(递归锁):一个线程可以多次获取同一把锁而不会产生死锁。比如:一个线程执行一个带锁的同步方法,该同步方法内部又调用一个需要相同锁的同步方法,那么会直接执行调用方法,而无需重新获得锁。Java中Lock和Syn都是可重入锁;
  3. 自旋锁:利用CAS实现;do–while()判断,符合条件就加锁或者解锁;
  4. 死锁:两个线程互相拥有着对方所需求的资源,且不释放;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值