多线程
基本知识
创建线程的常用三种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口(JDK1.5>=)
public class ThreadTest extends Thread {
@Override
public void run() {
System.out.println(this.getName() + "..开始..");
int i = 0;
// 中断标志位结束
while (!Thread.interrupted()) {
i++;
}
System.out.println(this.getName() + "..结束.." + i);
}
}
public class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println(" RunnableTest");
}
}
public class CallableTest implements Callable<String> {
@Override
public String call(){
System.out.println("CallableTest");
return "2CallableTest";
}
}
//睡眠 Thread.sleep(2000);
//加入到主线程 t1.join();
Thread t1 = new Thread(new ThreadTest());
t1.start();
t1.join();
Thread t2 = new Thread(new RunnableTest());
t2.start();
FutureTask t3 = new FutureTask(new CallableTest());
t3.run();
//获取返回值
String a = t3.get();
//中断标志位,若这里不中断,则在while循环中一直进行
t1.interrupt();
在大量数据时,设置优先级
t3.setPriority (6);
//最大优先级 十级 默认为五级 优先级
生命周期是那些?
主线是:新建–>可运行状态–>运行–>终止状态
运行时,会有三种阻塞:
- 其他阻塞:sleep()睡眠,等待时间到 ; join()合并线程,从并行到串行,
- 对象锁阻塞:synchronized:同步锁,获得锁
- 等待阻塞:wait();
sleep和wait区别?
sleep()方法是Thread类里面的,主要的意义就是让当前线程停止执行,让出cpu给其他的线程,但是不会释放对象锁资源以及监控的状态,当指定的时间到了之后又会自动恢复运行状态。
wait()方法是Object类里面的,让线程放弃当前的对象的锁,进入等待此对象的等待锁定池,只有针对此对象调动notify方法后本线程才能够进入对象锁定池准备获取对象锁进入运行状态。
为啥wait,notify,notifyAll不在thread类中?
wait()和notfity()必须在同步代码块里
对象级别,而不是线程级别!
notify和ntityAll区别: 唤醒一个,和唤醒全部
synchronized 和(Lock锁) ReentrantLock区别?
-
相似点:
加锁方式同步,都是阻塞式的同步:意思是说,A线程获得了锁进入代码块,其他的BCD全外面等着
-
区别:
synchronized 是关键字,lock()和unlock()成对出现.
synchronized 非公平锁, lock()默认非公平锁,可以设置为公平锁.性能一般
线程安全讲一下,有哪几种?
原子,可见,有序
线程安全就是,访问一段代码,产生的确定的结果!
多线程,单线程,执行下永远一致,那么就是线程安全.
-
不可变:
例:String,Integer,Long,这些final类型,任何一个线程都无法改变,除非新创建.
-
绝对线程安全:
不管运行时环境如何,不需要额外的同步措施,
-
相对线程安全:
通常意义上的线程安全,如Vector,Concurrent HashMap,add,remove操作等,不会被打断
-
线程非安全:
例如:ArrayList,LinedList,HashMap等
问:ArrayList线程安全吗?
答:不安全,可用 Vector,synchronizedList 或 加锁
问:HashMap线程安全吗?
答:不安全,可用Hashtable、synchronizedMap、ConcurrentHashMap。
线程池7参数:
- corePoolSize:线程池中的常驻核心线程数
- maximumPoolSize:线程池能够容纳同时执行的最大线程数
- keepAliveTime:多余的空闲线程的存活时间
- unit:存活时间单位
- workQueue:任务队列,被提交但尚未被执行的任务。
- threadFactory: 表示生成线程池中工作线程的线程工厂
- handIer:拒绝策略
线程池流程
4拒绝策略:
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- DiscardPolicy:丢弃任务,但是不抛出异常。
- DiscardOldestPolicy:丢弃队列最久的任务,然后重新提交被拒绝的任务
- CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
线程安全要保证,原子性,可见性,有序性
原子性:同步机制,加锁实现
可见性:volatile保证可见性
有序性:按照顺序执行
线程之间如何通信?
共享内存
同步?异步?阻塞?非阻塞?是啥?
客户端发送请求时
同步:客户端发送请求时,等待方法调用执行完,
异步:客户端发送请求时,不用等上一个方法调用完
阻塞,非阻塞,是服务端的处理,
阻塞:处理A时,其他BCD都需要等
非阻塞:可以处理其他的.
四个概念:
- BIO同步阻塞:效率不好,等全部完成,返回; 其他都给我等着
- NIO同步非阻塞:可以先处理其他的,
- 异步阻塞:不等,先做其他的,处理完 ,类似 ajxa
- AIO异步非阻塞:不等,处理其他请求.高效
IO流简单说下?

啥是锁?分为几类?
通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现_依丨稀的博客-CSDN博客
- 乐观锁?悲观锁?
- 偏向锁?->轻量级锁?(自旋锁?)->重量级锁?
- 升锁(锁膨胀)?降锁?锁竞争?
- 可重入锁(递归锁)?
- 公平锁?非公平锁?
- 可中断锁?
读写锁其实是一对锁,一个读锁(共享锁)和一个写锁(互斥锁、排他锁)。
降锁:只有在垃圾回收的时候才会降,可以理解为不可及降
原子性问题?
线程安全?
问:为啥会出现这种问题?
答:多线程操作共享数据导致的
锁:
-
悲观锁:synchronization , lock(AQS)
-
乐观锁:CAS(比较与交换,Compare and swap)
让每个线程玩自己的数据,互不打扰,ThreadLocal
- 加锁synchronized,保证同一时间只有一个线程操作变量,其他线程需等待操作结束才能使用临界资源
- 使用CAS操作,变量计算前保留一份旧值a,计算完成后结果值为b,把b刷到内存之前先比较a是否和内存中变量一致,如果一致,就把内存中的变量赋值为b,不一样,重新获取内存中变量值,重复一遍操作,一直到a和内存中一致,操作结束。
CAS底层实现?比较与交换 Compare-and-Swap
啥是CAS?
啥是CAS? 就是 比较与交换
先比较是否与预期值一致,
-
一致,交换,返回 ture;
-
不一致,拒绝,返回 false;
在java api方面可以看到,
//哪个对象?哪个属性的内存偏移量?oldValue,newValue
compareAndAwapObject(Object var1,long var2,Object var4,Object var4);
compareAndAwapInt(Object var1,long var2,Object var4,Object var4);
compareAndAwapLong(Object var1,long var2,Object var4,Object var4);
Java中真正的CAS操作调用的native方法,再往下就是C++内容。再往下就是汇编语言,cpu硬件底层支持比较与交换,若是多核,加lock,cpu层面的锁保证原子性.
因为整个过程中并没有“加锁”和“解锁”操作,因此乐观锁策略也被称为无锁编程。换句话说,乐观锁其实不是“锁”,它仅仅是一个循环重试CAS的算法而已!
ABA:
ABA不一定是问题,只是++,–的操作,不存在问题
线程A:A1->B2
线程B:B2->A3
线程C:A1->C4
java已经提供了,在修改value的同时,指定版号
自旋次数过多:
自旋次数多,会造成占用大量的CPU资源.
synchronization方向:从CAS几次失败后,会挂起(WAITING),避免占用资源
longAdder方向:基于分段锁的形式解决(看业务)
只针对一个属性保证原子性:
ReentrantLook基于AQS,AQS基于CAS.
四种引用类型+ThreadLocal的问题:
问:为什么要说四种引用和ThreadLocal的关系呢?
答:在ThreadLocal中,有个ThreadLocalMap,其中有entry有key-value
其中key是弱引用,value是强引用
为了区分,所以要提及四种引用
问:既然key是弱引用,那么为什么还会造成内存泄露?如何解决?
答:value是强引用,虽然key弱引用gc会回收,但是value不会回收。导致泄露,
答:用完以后 thread.remove();即可
四种引用类型:
强引用:User xx=new User(); xx就是强引用,只要引用还在,就不会回收.
软引用:单独用一个SofeReference引用的对象,就是软引用,如果内存不足,只有软引用回收
SofeReference xx=new SofeReference(new User);//软引用 User user=xx.get();//强引用 //因存在强引用,无法被回收;;引用只有软引用,才会被回收
弱引用:WeakReference引用的对象,一般就是弱引用,只要执行GC,就会回收只有弱引用的对象.可以解决内存泄露的问题,看ThreadLocal即可.
虚引用:PhantomReference引用的对象,就是虚引用,拿不到虚引用指向的对象,**一般监听GC回收阶段,或者是回收堆外内存时使用。**虚引用只是用来监视对象的回收。
可见性问题?
JAVA内存模型
(JVM内存模型和Java模型不是一个东西)
CPU从缓存拉数据,缓存有三级,L1,L2,L3,都没有则去主内存拉取,JMM就是CPU和主内存之间,协调。保证可见,有序等
缓存是CPU的缓存,CPU的缓存分为L1(线程独享),L2(内核独享),L3(多核共享)
JMM就是Java内存模型的核心,可见性,有序性都基于这实现。
主内存JVM,就是你堆内存。
jvm内存模型
有什么可以保证 可见性 ?
啥是可见性?
线程之间,对变量的变化是否可见
java层面:(可见:记录问题,可见性测试)
- volatile:保证cpu每次操作数据,都是主内存读写
- synchronization:保证前面操作的数据是可见的。
- lock:也可以保证CAS或者操作volatile的变量之后,可以保证前面操作的数据是可见的。
- final:是常量没法动
volatile修饰引用数据类型
首先volatile 修饰引用数据类型,只能保证引用数据类型的地址是可见的,不保证内部数据可见
注意:hotspot中实现,若换一个版本则效果不同,只用于面试,不能这么干活!
public class ConpanyTest {
static class A {
boolean b = true;
//volatile boolean b = true; 加这里,保证了b的可见性,这里会(结束)
void run() {
while (b) {}
System.out.println("结束");
}
}
static A a = new A();
//static volatile A a = new A(); 加这里,不保证内部数据可见.所以还是死循环
public static void main(String[] args) throws InterruptedException {
new Thread(a::run).start();
// new Thread(() -> a.run()).start();
Thread.sleep(10);
a.b = false;
}
}
有了MESI协议,为啥还有volatile?
MESI协议:硬件协议
volatile:软件的java中JMM的一致性
volatile的可见性底层 如何实现的?
volatile的底层生产的汇编lock指令,之歌指令会要求强行写入主内存,并且可以忽略Store Buffer这种缓存,从而达到可见性的目的,而且会利用MESI协议,让其他缓存失效.
有序性高频问题:
什么是有序性?
单例模式,懒汉机制.
懒汉为保证现场安全,会采用DCL方法,
但是,单用DCL,依然有几率出现问题.空指针
在Java编译.java为.class时,会基于 即时编译(JIT)做优化,将指令的顺序做调整,提升执行效率
在CPU层次,也会对一些执行进行重新排序,从而提升执行效率.
解决方案:加锁(还是加锁)synchronized和Lock,保证同一时刻只有一个线程进行操作
使用volatile修饰变量,在JMM中volatile的读和写都会插入内存屏障来禁止处理器的重排
volatile的有序性底层实现?
被volatile修饰的属性,在编译时,会在前后追加内存屏障
- SS:屏障前读写操作,必须全部完成,再执行后续操作
- SL:屏障前写操作,必须全部完成,再执行读操作
- LL:屏障前读操作,必须全部完成,再执行后续读操作
- LS:屏障前读操作,必须全部完成,再执行后续写操作
内存屏障在JDK规定的,目的是保证volatile修饰的属性不会出现指令重排的问题.
synchronization 问题?
synchronization 锁升级过程?
锁就是对象,随便哪一个都可以,Java中所有对象都是锁。
无锁(匿名偏向)、偏向锁、轻量级锁、重量级锁
无锁(匿名偏向): 一般情况下,new出来的一个对象,是无锁状态。因为偏向锁有延迟,在启动JVM的4s中,不存在偏向锁,但是如果关闭了偏向锁延迟的设置,new出来的对象,就是匿名偏向。
偏向锁: 当某一个线程来获取这个锁资源时,此时,就会变为偏向锁,偏向锁存储线程的ID
当偏向锁升级时,会触发偏向锁撤销,偏向锁撤销需要等到一个安全点,比如GC的时候,偏向锁撤销的成本太高,所以默认开始时,会做偏向锁延迟。
安全点:
- GC
- 方法返回之前
- 调用某个方法之后
- 甩异常的位置
- 循环的末尾
轻量级锁: 当在出现了多个线程的竞争,就要升级为轻量级锁(有可能直接从无锁变为轻量级锁,也有可能从偏向锁升级为轻量级锁),轻量级锁的效果就是基于CAS尝试获取锁资源,这里会用到自适应自旋锁,根据上次CAS成功与否,决定这次自旋多少次。
重量级锁: 如果到了重量级锁,那就没啥说的了,如果有线程持有锁,其他竞争的,就挂起。
synchronized锁粗化&锁消除?
锁粗化(锁膨胀):(JIT优化)
while(){
sync(){
// 多次的获取和释放,成本太高,优化为下面这种
}
}
//----
sync(){
while(){
// 优化成这样
}
}
锁消除:在一个sync中,没有任何共享资源,也不存在锁竞争的情况,JIT编译时,就直接将锁的指令优化掉。
4.3 synchronized实现互斥性的原理?
偏向锁:查看对象头中的MarkWord里的线程ID,是否是当前线程,如果不是,就CAS尝试改,如果是,就拿到了锁资源。
轻量级锁:查看对象头中的MarkWord里的Lock Record指针指向的是否是当前线程的虚拟机栈,如果是,拿锁执行业务,如果不是CAS,尝试修改,修改他几次,不成,再升级到重量级锁。
重量级锁:查看对象头中的MarkWord里的指向的ObjectMonitor,查看owner是否是当前线程,如果不是,扔到ObjectMonitor里的EntryList中,排队,并挂起线程,等待被唤醒。
4.4 wait为什么是Object下的方法?
执行wait方法需要持有sync锁。
sync锁可以是任意对象。
同时执行wait方法是在持有sync锁的时候,释放锁资源。
其次wait方法需要去操作ObjectMonitor,而操作ObjectMonitor就必须要在持有锁资源的前提的才能操作,将当前线程扔到WaitSet等待池中。
同理,notify方法需要将WaitSet等待池中线程扔到EntryList,如果不拥有ObjectMonitor,怎么操作!
类锁就是基于类.class作为 类锁
对象锁,就是new 一个对象作为 对象锁
设计模式(单例,工厂,代理,消费者生产者,策略,责任链,观察者,模板,装饰者),多线程,JVM,MySQL,Spring,SpringBoot,Redis,MQ
1
记录问题
多线程demo
存在两个问题,需要注意
public class Demo {
public static void main(String[] args) {
Ticket target = new Ticket();
for (int i = 0; i < 10; i++) {
Thread t1 = new Thread(new Ticket(), "售票" + i);
// Thread t1 = new Thread(target, "售票" + i);
//这里可以用(new Ticket(), "售票" + i);是因为 下面加了static
t1.start();
}
}
}
class Ticket implements Runnable {
private static int ticket = 100;
//static,全局变量
@Override
//public synchronized void run() {
//synchronized 加到方法上时候,并不能生效,对象锁,锁住了方法,没有锁住代码块
public void run() {
while (true) {
synchronized ("z") {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
} else {
System.out.println(Thread.currentThread().getName() + "售罄!!!");
break;
}
}
}
}
}
volatile 可见性测试
/** volatile 可见性测试--- volatile */
// static 只是说明这个变量为该类的所有实例所共有,但是线程访问该变量时还是从主内存拷贝一份回工作内存,至于什么时候同步回主内存那就不好说了。
//volatile 表示即使你从主内存拷贝到了工作内存,你每次要用的时候还是要去主内存同步,如果变量发生了改变就拿最新的,如果当前线程改变了这个变量也要写回到主内存。
/** volatile 可见性测试 */
public class VolatileTest {
static volatile boolean flag = true; // 用volatile修饰
public static void main(String[] args) throws InterruptedException {
new Thread(
() -> {
while (flag) {}
System.out.println("t1结束!");
})
.start();
Thread.sleep(100);
flag = false;
}
}
///
/** volatile 可见性测试 ---synchronized */
public class VolatileTest {
static boolean flag = true; // 不用volatile修饰
public static void main(String[] args) throws InterruptedException {
new Thread(
() -> {
while (flag) {
synchronized (VolatileTest.class) {
}
}
System.out.println("t1结束!");
})
.start();
Thread.sleep(100);
flag = false;
}
}
/** volatile 可见性测试---lock */
public class VolatileTest {
static boolean flag = true; // 不用volatile修饰
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(
() -> {
while (flag) {
lock.lock();
lock.unlock();
}
System.out.println("t1结束!");
})
.start();
Thread.sleep(100);
flag = false;
}
}