JUC
关键字:volatile
部分汇总
线程创建位置
都是先穿件好?不是有new就直接创建线程,创建不管其中的方法是否阻塞,都先穿件好(一定不是等一个线程完成了再穿件下一个)
基本原则:主线成创建线程,没有阻塞主线程,则主线程不管子线程的执行
创建线程的方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- FutrueTask
- 线程池
- 线程池方式
Constructor and Description |
---|
Thread() 分配一个新的 Thread 对象。 |
Thread(Runnable target) 分配一个新的 Thread 对象。 |
Thread(Runnable target, String name) 分配一个新的 Thread 对象。 |
Thread(String name) 分配一个新的 Thread 对象。 |
Thread(ThreadGroup group, Runnable target) 分配一个新的 Thread 对象。 |
Thread(ThreadGroup group, Runnable target, String name) 分配一个新的 Thread 对象,使其具有 target 作为其运行对象,具有指定的 name 作为其名称,属于 group 引用的线程组。 |
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 分配一个新的 Thread 对象,以便它具有 target 作为其运行对象,将指定的 name 正如其名,以及属于该线程组由称作 group ,并具有指定的 堆栈大小 。 |
Thread(ThreadGroup group, String name) 分配一个新的 Thread 对象。 |
FutureTask
- 调用futureTask.get(),如果线程没有计算完成,get没有值,等待线程执行完成,有值则直接返回
- 不调用get,主线程不会被阻塞,主线程直接结束,子线程也结束
FutureTask作用/原理
- 主线程继续
- 开启子线程执行
- 主线程获取子线程结果
[!WARNING]
futureTask的get方法是阻塞方法,无论使不使用isDone,主程序都会等线程进行完成并返回结果,通过get获取.
线程的执行和get无关,线程执行完成使得get获取到结果,但是一旦get有值之后,再次调用直接获取值(线程已经执行结束了)
如果不调用get,则主线程不会被阻塞
辅助类
- 阻塞主线程,等待子线程——CountDownLatch
- 阻塞各个子线程,指定各个子线程阻塞位置和阻塞子线程数————CyclicBarrier
- 指定"同时"能有几个线程允许访问资源,其余线程阻塞(相当于同时发几个锁)————Semaphore
在[Java](https://www.baidu.com/s?tn=84053098_3_dg&wd=Java&usm=1&ie=utf-8&rsv_pq=d27c70f300152f55&oq=future get 阻塞原理&rsv_t=242bzBP8kvtrlMXu4O2LMbF5GaZ6EfLw%2BS9qZaqYmaYb%2BUrTzRKN0m6QipIjyMw1Hh7aTA&sa=re_dqa_generate)中,Future表示异步计算的结果,get()方法用于等待Callable任务结束并获取执行结果。如果任务还未完成,get()方法会阻塞当前线程,直到任务完成并返回结果。
多线程编程步骤
- 穿件资源类
- 资源类操作方法
- 判断
- 干活
- 通知
- 创建多个线程,调用资源类方法
- 防止虚假唤醒
Package java.util.concurrent
线程不安全
- arrayList
- HashSet
- HashMap
解决方案
- juc包下的写时复制技术
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- ConcurrentHashMap
- 集合工具包穿件带锁的集合
- Collections.synchronizedList
- Collections.synchronizedSet
- Collections.synchronizedMap
synchronized
- 非公平锁
- 同步锁对象是谁
基本概念
线程VS进程
- **进程:**是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程 设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的 描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活 动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
- **线程: 操作系统能够进行运算调度的最小单位。**一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每条线程并行执行不同的任务
线程状态
NEW
——新建
RUNNABLE
——准备就绪
BLOCKED
——阻塞
WAITING
——不见不散啊
TIMED_WAITING
——过时不候
TERMINATED
——终结
wait()Vsleep()
wait() | sleep() |
---|---|
Object的方法 | Thread的静态方法 |
会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中) | 不会释放锁,它也不需要占用锁 |
它们都可以被interrupted方法中断。
并发与并行
- **并发:**同时刻多线程访问同一资源
- **并行:**多项工作同时执行,最后再汇总
管程
- Monitor监视器——锁
- 一种同步机制,保证同一时间,只有一个线程访问被保护数据/代码
- JVM同步基于进入和退出,使用管程对象实现的
用户线程和守护线程
属性(Daemon,可以进行设置)
(true)用户线程:自定义线程;主线程结束,用户线程还运行,jvm存活
守护线程:如垃圾回收,运行再后台;主线程结束,JVM结束
Lock接口
Package java.util.concurrent.locks
synchronized
多线程步骤
- 创建资源类(属性+方法)
- 创建多个线程,调用资源类操作方法
创建线程的方式
- 继承Thread类
- 实现Runnable接口
匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
//调用执行方法
tickets.sale();
}
}, "saler2").start();
Thread saler1 = new Thread(new Runnable() {
@Override
public void run() {
//调用执行方法
tickets.sale();
}
}, "saler1");
saler1.start();
new Thread(
()->{
for (int i = 0; i < 40; i++) {
tickets.sale();
}
}, "saler1").start();
- 使用Callable接口
- 使用线程池
- @FunctionalInterface——函数式接口——可以使用lamda表达式
- 调用start的时候,线程不一定被创建好了(由操作系统决定)
[!IMPORTANT]
synchronized——自动上锁解锁
Lock接口
java.util.concurrent.locks.ReentrantLock
ReentrantLock——可重入锁
synchronized VS Lock
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock() 去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
性能竞争不激烈的时候,两者性能差不多,而系统资源竞争激烈的时候(大量线程),Lock性能更优
线程间通信
demo3ThreadCommunication
[!WARNING]
调用start的时候,线程不一定被创建好了(由操作系统决定)
资源类操作方法
- 判断
- 干活
- 通知:通知其他线程执行
synchronized实现
- 类的方法
- 条件不满足等待
wait()
作用?等待多久?————等待下次抢到资源,在原地唤醒 - 通知
notify()
notifyAll()
public synchronized void incr() throws InterruptedException {
//条件判断
if (number != 0){
System.out.println(Thread.currentThread().getName()+": 条件不满足 - 等待...");
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+": 加1操作 - 结果:"+number);
//操作完成——通知其他线程操作
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
//条件判断
if (number != 1){
System.out.println(Thread.currentThread().getName()+": 条件不满足 - 等待...");
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+": 减1操作 - 结果:"+number);
//操作完成——通知其他线程操作
this.notifyAll();
}
线程通信问题
条件写在if中可能会出现虚假唤醒——建议写在循环中(while循环判断条件)(其实也可以使用if-else)
wait特点——在哪里wait()就会在哪里唤醒不会从头执行
while(condition){
wait()}
Lock实现
条件调用等待、通知
condition.await()
——等待
condition.signalAll()
——通知`
基本步骤
- 定义重入锁
- 通过锁创建条件
- 上锁
- 条件判断——等待
- 干活
- 通知
- 解锁
private Integer number = 0;
//实例锁
private final ReentrantLock lock = new ReentrantLock();
//实例化条件
private final Condition condition = lock.newCondition();
public void incr(){
lock.lock();
try{
while (number!=0){
condition.await();
}
System.out.println("@"+Thread.currentThread().getName()+" 加1操作, result = "+(++number));
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally{
condition.signalAll();
}
}
线程间的定制化通信
线程按照指定的执行顺序进行调用(上面谁抢到并满足条件就执行)
- 使用标识位作为执行条件
- 为每个线程创建一个条件
- 完成通知(唤醒)拥有指定的condition(钥匙)的线程
案例:
AA打印5次->BB打印10次->CC打印15次——5个轮回
-
使用标识位作为执行条件
-
每个方法定义一个标识位,加入标识位变量,执行完成改变标识位,指定下一方法的标识位(标志位作为条件)(不同线程调用不同的方法)
为每个线程创建一个condition(相当于钥匙)
condition2.signal();通知condition2的调用者
public class PrintResource {
//定义标志位
private int flag = 1;
//创建锁
ReentrantLock lock = new ReentrantLock();
//创建不同的条件
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void print5(int epcho){
lock.lock();
try{
while (flag!= 1){
condition1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"::i -- 第 "+epcho + "轮");
}
//完成内容,更改标志位
flag=2;
//通知c2
condition2.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally{
lock.unlock();
}
}
public void print10(int epcho){
lock.lock();
try{
while (flag!= 2){
condition2.await();
}
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName()+"::i -- 第 "+epcho + "轮");
}
flag=3;
condition3.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally{
lock.unlock();
}
}
public void print15(int epcho){
lock.lock();
try{
while (flag!= 3){
condition3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"::i -- 第 "+epcho + "轮");
}
//通知
flag=1;
//通知c2
condition1.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally{
lock.unlock();
}
}
}
集合线程安全
ArrayList
add没有锁
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
Exception in thread "第16个线程" java.util.ConcurrentModificationException
解决方案:
- Vector
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}
- Collections创建带锁list
Collection<String> list = Collections.synchronizedList(new ArrayList<>());
- JUC包中的CopyOnWriteArrayList(写时复制技术)推荐
即兼顾了并发读,也兼顾了并发写
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
HashSet
add同样没有锁 synchronized
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
解决方案
- JUC包中的CopyOnWriteArraySet(写时复制技术)推荐
CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
HashMap
解决方案
ConcurrentHashMap<>()
public static void main(String[] args) {
//HashMap线程不安全
// HashMap<String, String> hashMap = new HashMap<>();
//解决方案
ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<>();
//多线程操作
for (int i = 0; i < 40; i++) {
new Thread(()->{
hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));
System.out.println(hashMap);
},"第"+i+"线程").start();
}
}
多线程锁
synchronized
实现同步的基础:Java中每个对象都可以作为锁
表现形式:
- 对于普通方法,锁时当前实例对象
- 对于静态方法,锁是当前类的Class对象
- 对于同步方法块,锁是synchronized括号里配置的对象
在分析多线程阻塞与非阻塞的时候,就看其同步的锁是谁,谁拥有锁
公平锁和非公平锁
ReentrantLock(boolean fair)
——默认false
对比
非公平锁
- 线程饿死
- 效率高
谁抢到锁谁有有权进行加锁操作
公平锁
- 线程访问资源均衡
- 效率相对较低
将抢锁的线程放入队列,依次出队列加锁
源码
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁/递归锁
demo8
可重入就是说某个线程已经获得某个锁,可以再次获取该锁而不会出现死锁。
synchronized
和ReentrantLock
都是可重入锁
synchronized
public static void main(String[] args) {
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(Thread.currentThread().getName()+"第一层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+"第二层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+"第三层");
}
}
}
},"synchronized_thread").start();
}
ReentrantLock
static private void op(){
try{
reentrantLock.lock();
System.out.println(Thread.currentThread().getName()+&