1,进程,线程,协程:
进程:系统分配的基本资源单元,可以将一个可执行文件后软件看作一个进程
线程:一个进程包含多个线程,一个软件中有多个执行指令,一个线程就是一个指令
协程(虚拟线程):这是一个比线程更轻量级的设置,这个不由操作系统调度,而是有jvm,所以会更快
一个cup内核与一个线程在数量关系上是1:1,
进程进行分配资源(像cpu,磁盘等),一个进程有多个线程,一个线程中有多个协程,多个线程可以共享进程中的资源,多个进程之间不会共享全局变量,多个线程之间可以共享全局变量。
2,并发(concurrent),并行(parallel),串行
并发:一个内核处理多个线程,使用轮询的方式让用户几乎感知不到其实他们是具有先后顺序的,(换 句话来理解就是,一个核心在计算好的时间片中进行上下文切换)。
并行:多个内核分别处理多个线程,这样的话,线程之间的确是同时执行的,
串行:多个线程排队一次被被内核处理
其实也就是说并发是交替执行,而并行是同时进行,串行是排队执行。

2,线程的生命周期:
创建(new)--》就绪(runnable)--》运行(running)--》阻塞(block)--》等待(wait)--》超时等待(timeout wait)--》终止(terminated)
创建线程的两种方式:可以通过继承thread或者实现runnable,但建议使用runnable因为这样扩展性更强,因为继承的话只能是单一的(因为Java不支持多继承),而是用runnable的话可以单一性,也就说可以同时实现多个,并且使用继承thread的话,每次都创建thread对象会浪费资源,但是runnable的话多个线程可以共享一个runnable对象,这不仅节约资源并且也更符合设计原则。
其实还有一种创建线程的方式,实现callable接口,这个接口让创建的线程有返回值并且可以抛出异常。
3,线程中的start与run方法的区别:
start()是异步的,他不会按照的代码书写顺序依次执行,也就说是非阻塞的;
run()就和普通函数方法一样一次按顺序执行。并且start()只能执行一次在线程没有被销毁的情况下,run()方法可以多次执行。
4, 线程中常用的方法:
除了上面介绍的还有sleep()睡眠,虽然这个方法在开发时很少使用,这个方法会按照设定的时间阻塞线程,也就说在这段时间内,该线程不用使用cpu资源,在平时使用这个方法时会要求抛出interruptedException异常。
join():让一个线程等待另一个线程执行完。
intertupt():会中断当前线程,但不会终止当前线程(给当前线程打了一个标记)。
isintercupt():检查当前线程中断状态。
currntThread():获取当时形成的引用。
setname()和getname():设置和获取当前线程名称。
getId():获取当前线程的id。
getState():获取当前线程状态。
setDaemon():设置该进程为守护进程或用户进程。守护进程:当所有非守护进程结束后守护进程自动结束。
yieid():暂停进程,并让拥有相同级别的进程运行。
setPriority():设置线程的优先级,范围是1--10,默认是5
interrupted():(静态方法)会中断当前线程,但不会终止当前线程,并将中断标记清除。
isalive():判断当前线程是否存活。
5,锁:
这是多线程的一种保护共享资源的同步机制,当一个线程得到锁后,其他线程必须等待该线程释放锁
(1)按锁的种类分类:
乐观锁:可以通过版本号或CAS来检测冲突(适合读写少的场景下,因为乐观锁它可能不上锁,它在读取自己副本内容时,会先加载主内存的数据,检查是否被修改后才会读出来。)(这里说一下CAS,全称:Compare-And-Swap:比较并交换,Java高并发中核心无锁算法)
悲观锁:默认并发冲突会发生,访问资源前先加载锁(适合的场景是写操作的量过大时,让多余的写操作等待)
自旋锁:线程不阻塞,循环尝试获取锁

(2)按锁的特性分类:
公平锁:Reentrantlock(true):按顺序访问资源
非公平锁:Reentrantlock(false):允许插队,吞吐量更高
共享锁:Reentrantreadwritelock.readlock:允许多个线程同时度,但写时独立
独占锁(排他锁):synchronized,reentrandlock同一时刻只有一个线程拥有锁
在默认情况下,程序采用了非公平锁,这样设置的好处是为了防止cpu频繁的切换上下文,减少cpu的性能开销,也可以预防锁金额现象的产生。
6. 线程池:避免重复的创建和销毁锁
(1)优点: a,避免资源的浪费;b,提高效应速度;c,提高线程管理;d,提高任务调度的灵活性。
(2)主要参数:

这里详细说明workqueue(任务队列或者称阻塞队列):
a,arrayblockingqueue:基于数组的有界阻塞队列,这种队列按照FIFO顺序对元素排序
b,linkedblockingqueue:基于链表的有界阻塞队列,也是按照FIFO对元素进行排序,使用这个队列通常不设置大小,但是其有默认的大小(Integer.MAX_VALUE),executors的静态方式一般都使用这个。
c,synchronousqueue:这是一个同步的阻塞队列,在这个队列中当一个线程被读时,另一个线程正在被写。注:吞吐量通常要⾼于LinkedBlockingQueue。静态⼯⼚⽅法Executors.newCachedThreadPool使
⽤了这个队列。
d,delayqueue:支持延迟的无界阻塞队列,底层是基于priorityqueue实现。
e,priorityblockingqueue:这是一个具有优先级顺序的无序队列,并且其会自动扩容。
注:可以使用put添加元素,但是用take方法时,如果队列元素为空,则不能插入空元素,并且插入的元素必须可以进行比较的(comparable),不然就会报错。
这里梳理一下线程池创建和执行的流程:

(3),sumbit和execute的区别;

(4),拒绝策略:当进来的线程数量达到了最大线程数,并且阻塞队列中也无法容纳时,就必须使用拒绝策略,合理设置拒绝策略预防oom危险。
jdk内置的拒绝策略有以下:
a,abortpolicy(默认):会直接抛出rejectedexectionexception异常,
b,callerrunpolicy:调用者运行;该机制不会直接抛出异常,但也抛弃任务,而是将任务回退给调用者。
c,discardordestpolicy:会移除队列中等待最久的任务,然后将当前任务再次提交。
d,diacardpolicy:会默默丢弃无法执行的任务,不会抛出异常。
上面这些拒绝策略均由rejectexcutionhandle接口
(5)生产中合理设置参数
要想合理的配置线程池,需要先分析任务的特性,可以从以下⼏个⻆度分析:
任务的性质:CPU密集型任务、IO密集型任务和混合型任务
任务的优先级:⾼、中、低
任务的执⾏时间:⻓、中、短
任务的依赖性:是否依赖其他的系统资源,如数据库连接。
性质不同任务可以⽤不同规模的线程池分开处理。CPU密集型任务应该尽可能⼩的线程,如配置cpu数量
+1个线程的线程池。由于IO密集型任务并不是⼀直在执⾏任务,不能让cpu闲着,则应配置尽可能多的线
程,如:cup数量*2。混合型的任务,如果可以拆分,将其拆分成⼀个CPU密集型任务和⼀个IO密集型
任务,只要这2个任务执⾏的时间相差不是太⼤,那么分解后执⾏的吞吐量将⾼于串⾏执⾏的吞吐量。可
以通过 Runtime.getRuntime().availableProcessors() ⽅法获取cpu数量。优先级不同任务
可以对线程池采⽤优先级队列来处理,让优先级⾼的先执⾏。
使⽤队列的时候建议使⽤有界队列,有界队列增加了系统的稳定性,如果采⽤⽆解队列,任务太多的时
候可能导致系统OOM,直接让系统宕机。
线程池汇总线程⼤⼩对系统的性能有⼀定的影响,我们的⽬标是希望系统能够发挥最好的性能,过多或
者过⼩的线程数量⽆法有消息的使⽤机器的性能。咋Java Concurrency inPractice书中给出了估算线程
池⼤⼩的公式:
Ncpu = CUP的数量 Ucpu = ⽬标CPU的使⽤率,0<=Ucpu<=1 W/C = 等待时间与计算时间的⽐例 为保存处理器达到期望的使⽤率,最有的线程池的⼤⼩等于: Nthreads = Ncpu × Ucpu × (1+W/C)
. CPU密集型:
System. out .println(Runtime. getRuntime ().availableProcessors());
IO密集型
a. 由于IO密集型任务线程并不是⼀直在执⾏任务,则应配置尽可能多的线程,如**CPU核数 2 。
看公司业务是CPU密集型还是IO密集型的,这两种不⼀样,来决定线程池线程数的最佳合理配置数。
(6)线程中的两个关闭方法
shutdown:线程池中不会再接收新的任务,执行完线程中的任务后关闭线程池。
ShutDownNow:会移除在队列中等待的线程,将正在进行的线程任务结束后,关闭线程池。
调用上面两种方法后均会遍历线程池中的所有线程,调用每个线程的中断方法(interrupt),然后使得isShutDown返回true,当所有线程结束后才会调用isTerminaed关闭线程池。
(7)线程池还有两种可能会关掉线程
a,当线程池中的任务全都执行完后,那空闲的线程会到阻塞队列中等待任务的到来,但因为我们设置了最大线程数,所以多余的线程会被回收,当他们去拿任务然后得到的结果是null时,会自动结束线程,这个过程是cas自动竞争的,直到只剩下核心线程数数量。
b,当一个线程执行任务过程中遇到异常情况时,该线程也会自动结束,
6,JMM(内存模型):定义了线程如何与内存交互,以及线程之间如何通过内存交互
a,JMM要解决的问题是:线程中变量的可见性:也就说在同一进程多个线程中,若果一个线程中将变量改变,其他线程是否可以看见
线程中操作原子性:一个线程中的方法块中只能执行一个操作(例如i=0)
线程中的指令有序性;按照指定顺序执行
b,JMM将内存分为了两个部分,一个时主内存,用来存储所有的共享变量;另一个是工作内存,用来存储线程中变量副本
线程对变量的所有操作都必须在工作内存中进行,不能直接在主内存中进行
c,JMM对内存的操作:8中原子操作
lock(锁定):作用于主内存变量,标识为线程独占
unlock(解锁):作用于主内存变量,释放锁定状态
read(读取):从主内存传输变量到工作内存
load(载入):把read得到的值放入工作内存的变量副本中
use(使用):把工作内存中的变量值传递给执行引擎
assign(赋值):把从执行引擎接收的值赋给工作内存中的变量
store(存储):把工作内存中的变量值传送到主内存
write(写入):把store操作得到的值放入主内存变量中
d,happens-before规则:JMM定义了可见性的规则
程序顺序规则:同一线程中的操作,前面的happens-before后面的
锁规则:解锁操作happens-before后续的加锁操作
volatile规则:volatile写操作happens-before后续的读操作
传递性规则:A happens-before B,B happens-before C,则A happens-before C
线程启动规则:Thread.start() happens-before新线程的所有操作
线程终止规则:线程的所有操作happens-before其他线程检测到该线程已终止
中断规则:对线程interrupt()的调用happens-before被中断线程检测到中断
(1)如何保证线程的原子性,有序性,可见性呢
可以使用以下几个关键字;

7,解决线程安全
a,使用threadlock:这是一个线程级别的变量,允许线程创建自己的变量副本,他与普通的变量相比,普通变量是将变量存放在堆中,而其是将变量存放在线程中,进而实现更好的封闭性;但是在使用其时要进行手动的调用remove()方法,不然会发生内存泄漏等危险问题。
b,InheritHhreadLodk:这是ThreadLock的子类,允许继承TreadLock
8,原子类:对于高并发三大特性之一的原子性,我们为了保证操作的原子性,不能同时使用多个操作,例如i++;而我们日常开发中又需要这样的操作,所以使用java.util.concurrent.atomic这个专为处理高并发的工具包来解决
Java原子类分类总表
类别 类名 用途 示例方法
基本类型原子类 AtomicBoolean 原子更新boolean类型 compareAndSet(boolean expect, boolean update)
AtomicInteger 原子更新int类型 incrementAndGet(), addAndGet(int delta)
AtomicLong 原子更新long类型 getAndDecrement(), accumulateAndGet(long x, LongBinaryOperator func)
数组类型原子类 AtomicIntegerArray 原子更新int[]数组中的元素 getAndSet(int i, int newValue)
AtomicLongArray 原子更新long[]数组中的元素 compareAndExchange(int i, long expect, long update)
AtomicReferenceArray 原子更新引用类型数组(Object[]) set(int i, T newValue)
引用类型原子类 AtomicReference 原子更新单个对象引用 getAndUpdate(UnaryOperator updateFunction)
AtomicStampedReference 带版本号的引用(解决ABA问题) compareAndSet(V expected, V newValue, int expectedStamp, int newStamp)
AtomicMarkableReference 带标记位的引用(用boolean标记状态) attemptMark(V expectedReference, boolean newMark)
字段更新原子类 AtomicIntegerFieldUpdater 原子更新类的volatile int字段 lazySet(T obj, int newValue)
AtomicLongFieldUpdater 原子更新类的volatile long字段 compareAndSet(T obj, long expect, long update)
AtomicReferenceFieldUpdater 原子更新类的volatile引用字段 getAndSet(T obj, V newValue)
高性能累加器(JDK8+) LongAdder 高并发下替代AtomicLong,分散竞争压力(适合统计场景) sum(), reset()
DoubleAdder 高并发下替代AtomicDouble(非精确计算) add(double x), doubleValue()
LongAccumulator 支持自定义累加规则(如取最大值) accumulate(long x)
DoubleAccumulator 支持自定义双精度累加规则 getThenReset()
9,保证线程安全的手段之一:将数据改变为只读类型
a,常见的方式是将基本或引用类型加上final关键字,(final修饰的集合也是可变的)
b,使用不可变集合或者collections工具类



10,wain与sleep的区别
所属方法:wait属于object,而sleep所属线程
参数值不同,wait不设置参数时,将永久睡眠,而sleep则必须设置参数
唤醒方式,wait当不设置参数时需要手动唤醒,而sleep自动唤醒,
释放锁,sleep不放释放锁,而wait释放锁并进入队列中等待
语法不同,sleep可以直接线程方法调用,而wait需要放在synchronied关键字中
11,使用wait和notify来实现生产者与消费这模式
流程图:

使用wait与notify实现生产-消费模型
import java.util.LinkedList;
import java.util.Queue;
public class PAC {
private final int MAX_NUM = 5;
private final Queue<Integer> queue = new LinkedList<>();
private final Object lock = new Object();
private volatile boolean running = true;
class Producer extends Thread {
@Override
public void run() {
try {
while (running) {
synchronized (lock) {
// 检查队列是否已满
while (queue.size() >= MAX_NUM) {
System.out.println("队列已满,生产者等待...");
lock.wait();
}
// 生产数据
int item = queue.size() + 1;
queue.add(item);
System.out.println("生产者添加: " + item + ",队列大小: " + queue.size());
// 通知消费者
lock.notifyAll();
}
Thread.sleep(500); // 模拟生产耗时
}
} catch (InterruptedException e) {
System.out.println("生产者被中断");
}
}
}
class Consumer extends Thread {
@Override
public void run() {
try {
while (running) {
synchronized (lock) {
// 检查队列是否为空
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待...");
lock.wait();
}
// 消费数据
int item = queue.poll();
System.out.println("消费者取出: " + item + ",队列大小: " + queue.size());
// 通知生产者
lock.notifyAll();
}
Thread.sleep(1000); // 模拟消费耗时
}
} catch (InterruptedException e) {
System.out.println("消费者被中断");
}
}
}
public void shutdown() {
running = false;
}
public static void main(String[] args) throws InterruptedException {
PAC p = new PAC();
// 创建多个生产者和消费者
Producer[] producers = new Producer[3];
Consumer[] consumers = new Consumer[2];
for (int i = 0; i < producers.length; i++) {
producers[i] = p.new Producer();
producers[i].start();
}
for (int i = 0; i < consumers.length; i++) {
consumers[i] = p.new Consumer();
consumers[i].start();
}
// 让程序运行一段时间
Thread.sleep(10000);
// 优雅关闭
p.shutdown();
// 中断所有线程
for (Producer producer : producers) {
producer.interrupt();
}
for (Consumer consumer : consumers) {
consumer.interrupt();
}
}
}
11,synchronized锁升级
无锁-->偏向锁-->轻量级锁-->重量级锁
创建线程时进入无锁状态,当另一个线程想要竞争该锁时,会升级为偏向锁,当多个线程竞争自旋到一定数量时会升级为轻量级锁,未获取锁的线程进入阻塞队列有操作系统将其升级为重量级锁,
12,synchronized与reentrantlock区别
synchronized是隐式的,他会自动上锁和释放锁,底层有jvm实现;
而reentrantlock是显式的,需要手动的调用lock上锁和使用unlock来释放锁,底层有jdk实现,他支持锁申请等待限时(可以设置等待时间),也就是说,当尝试获取锁失败后不会进入阻塞队列继续等待而是结束线程,reentantlock可以打断正在等待的线程,当ReentranLock中调用lockinterruptibly时,线程中调用interrupt时,会发生interruptException异常,reentrantlock更高的灵活性在更高级的开发中常用,但现在常用synchronized。

被折叠的 条评论
为什么被折叠?



