JUC 进阶
1. JUC主要的四个包
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
- java.util.concurrent.function
2. 线程和进程
- 进程是程序的一次执行过程;
- 线程是比进程更小的执行单位,一个进程可以有多个线程,线程也称为轻量级进程;
- 进程间一般相互独立,同类线程之间可以共享资源;
- Java默认有两个线程:main线程和gc线程;
并发和并行
- 并发是交替执行;
- 并行是一起执行。
线程的状态
- New
- Runnable:- Ready, - Running
- Blocked
- Waiting
- Timed_Waiting
- Terminated
wait()和sleep()的区别
- 属于不同的类:wait属于Object类,sleep属于Thread类;
- 二者都可以暂停线程的运行;
- sleep不释放锁,wait释放锁;
- sleep自动苏醒,wait需要被其他线程唤醒;
- sleep主要用于线程的暂停,wait用于线程间的交互通信;
3. Lock锁
公平锁,非公平锁
- 公平锁:十分公平,先来后到
public ReentrantLock(boolean fair){
sync = fair ? new FairSync() : new NonfairSync();
}
- 非公平锁 (默认):不公平,可以插队
public Reentrantlock(){
sync = new NonfairSync();
}
Lock锁三部曲
- 创建锁:new ReentrantLock();
- 上锁:lock();
- 解锁:unlock()
Synchronized 和Lock的区别
- Syn是关键字,Lock是一个类;
- Syn无法判断锁的状态,Lock可以判断是否获取到了锁;
- Syn自动释放锁,Lock手动释放锁;
- Syn是非公平锁,Lock可以自己设置;
- Syn适合锁少量的同步代码,Lock适合锁大量的代码。
4. 生产者消费者模式
-
注意使用
while()
避免虚假唤醒问题; -
使用
comdition
可实现精准唤醒:condition1.signal(), condition2.signal();
5. 八锁问题分析
链接点击跳转
6. 集合不安全类
- ArrayList不安全, 解决方案:
List<String> list = new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
- HashSet不安全:
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
- 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()的区别
- execute()只能提交Runnable类型的任务,而submit()可以提交Runnable和Callable类型的任务;
- execute()提交不需要返回值的任务,submit()用于提交需要返回值的任务。
8. 常用辅助类
- CountDownLatch
原理:每次有线程调用countDown()就-1,当计数器变为0,await()就会被唤醒,继续向下执行;
countDownLatch.countDown(); //数量-1
countDownLatch.await(); //等待计数器归零,再向下执行
- CyclicBarrier
作用与countDownLatch相似;
- 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(); //尝试和最早的竞争,不抛异常
最大线程数如何设置?
- CPU密集型:CPU是几核,就等于几,可保持CPU的效率最高;
- IO密集型:一般是程序中十分耗费IO资源线程数的两倍;
12. 四大函数式接口
函数式接口:只有一个函数的接口,如 Runnable;
- 函数型接口:两个参数,一个输入参数,一个输出参数
public interface Function<T, R>{
R apply(T t);
}
- 断定型接口:只有一个输入参数,返回值为boolean型
public interface Predicate<T>{
boolean test(T t);
}
- 消费型接口:只有一个输入参数
public interface Consumer<T>{
void accpet(T t);
}
- 供给型接口:只有一个返回值
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关键字标识这个变量是不稳定的,每次都要在主内存中进行读取。
八种内存交互操作(必须成对出现):
- lock(),
- unlock(),
- read(),
- load(),
- use(),
- assign(),
- write(),
- store()
17. Volatile
- 保证可见性
- 不保证原子性
- 避免指令重排
不使用lock和synchronized如何保证原子性?
答:使用原子类java.util.concurrent.atomic
import java.util.concurrent.atomic;
private volatile static AtomicInteger atomicInteger = new AtomicInteger();
Volatile 和Synchronized的区别
- volatile是线程同步的轻量级实现,性能要比Syn好,但volatile只能修饰变量,而Syn能修饰方法和代码块;
- volatile可以保证可见性,但不能保证原子性,Syn都可以保证;
- 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. 各种锁的理解
- 公平锁,非公平锁:是否可以插队;
- 可重入锁(递归锁):一个线程可以多次获取同一把锁而不会产生死锁。比如:一个线程执行一个带锁的同步方法,该同步方法内部又调用一个需要相同锁的同步方法,那么会直接执行调用方法,而无需重新获得锁。Java中Lock和Syn都是可重入锁;
- 自旋锁:利用CAS实现;do–while()判断,符合条件就加锁或者解锁;
- 死锁:两个线程互相拥有着对方所需求的资源,且不释放;