介绍的内容:
主要介绍多线程的基本概念到后面的基本使用。以及一些稍微的原理猜测。个人笔记,不喜勿喷
目录
目录
一、线程使用
1.1 概念
1.2 线程生命周期
1.3 java中实现的方式
1.3.1 继承线程 Thread
1.3.2 实现接口Runnable (无返回值)
1.3.3 实现接口Callable (带返回值)
1.4线程执行的流程
二、多线程带来的安全性问题
三、如何解决安全性问题
3.1 基本概念
3.2 如何自己实现上锁操作
四、线程相关使用工具
4.1相关同步API(了解)
4.2线程池相关的知识点(掌握)
4.2.1 ThreadPoolExecutor相关参数
4.2.2 线程池提供静态创建
4.2.3猜想一下线程池的实现原理
4.2.4其他
线程:主要是异步+并行,为了合理的利用cpu资源
一般多线程都会和并发、并行联系在一起。
并发:单个时间内,能支持的吞吐量。一定需要多线程的参与, 常见的评判指标有:QPS,TPS来进行评判标准
并行:同一时刻能够运行多个任务
图:
// 方式一 继承线程 Thread
public class ThreadDemoOne extends Thread{
@Override
public void run() {
System.out.println("继承 Thread ==> 相关业务逻辑");
super.run();
}
}
// 方式二 实现接口Runnable (无返回值)
public class ThreadDemoTwo implements Runnable{
@Override
public void run() {
System.out.println("实现接口Runnable ==>相关业务逻辑");
}
}
// 方式三 实现接口Callable (带返回值)
public class ThreadDemoThree implements Callable{
@Override
public Object call() throws Exception {
System.out.println("实现接口Callable ==>相关业务逻辑");
return null;
}
}
从程序发出指令之后cpu执行的简短流程
图形:
特性:原子性、有序性、可见性
原子性:多线程的不确定性,对资源的抢夺是不确定的,比如:多线程count++中
有序性:这个就涉及到指令重排序。涉及到cpu的高速缓存L1 L2、代码的顺序和执行的顺序不一定是一样的。针对一个变量。在cpu中如果需要,可能会优先运行。new Person -->指令分为三步
可见性:a理论上,所有的资源应该是需要从内存中读取,但是由于cpu高速缓存,导致修改之后的数据没有回写给内存。b:指令优化等 volatile 可以处理这种问题。 true -->另开线程 false
针对带来的安全性问题,我们可以采用相关的策略。更多API可以查看 第四点
可见性涉及到cpu内部高速缓存,导致内存数据未及时更新。多线程之间数据的不可见性。
volatitle:解决可见性,防止cpu缓存,让缓存失效。
1.怎么实现一个锁?需要考虑什么问题呢?如下:
1.条件互斥,共享资源(cas)
cas实现
2.等待队列(抢夺资源的线程存放)
BlockingQueue,链表
3.阻塞
sleep/wait/join/park
4.唤醒
notify/notifyall/unpark
线程同步类 CountDownLatch Semaphore CyclicBarrier
并发集合类 ConcurrentHashMap .ConcurrentSkipListMap , CopyOnWriteArrayList ,BlockingQueue
线程池: Executors
锁:StampedLock(1.8引入) ReentrantLock
原子操作:LongAdder (1.8引入,比AtomicLong 好)
// 实现runnable,或者继承Thread ,返回异常给调用方捕捉。如果实现(Callable接口可以返回参数就不用了)
Thread.currentThread().setUncaughtExceptionHandler();
线程使应用能够更加充分合理地协调利用CPU 、内存、网络、1/0 等系统资源。线程的创建需要开辟虚拟机栈、本地方法枝、程序计数器等线程私有的内存空间。在线程销毁时需要回收这些系统资源。频繁地创建和销毁线程会浪费大量的系统资源,增加并发编程风险。另外,在服务器负载过大的时候,如何让新的线程等待或者友好地拒绝服务?这些都是线程自身无法解决的。所以需要通过线程池协调多个线程, 并实现类似主次线程隔离、定时执行、周期执行等任务。
corePoolSize:常驻核心数。不会销毁的线程
maximumPool:线程池能够容纳同时执行的最大线程数
keepAliveTime:线程池中的线程空闲时间,当空闲时间达到keepAliveTime 值时,线程会被销毁
TimeUnit:keepAliveTime的时间单位通常是TimeUnit.SECONDS 。
workQueue:缓存队列。当请求数大于线程池能够容纳同时执行的最大线程数时。进入缓存队列
ThreadFactory:线程工厂,分组名称。方便后期出问题时精确定位
handler:执行拒绝策晤的对象。当缓存队列达到上限时,开始执行拒绝策略 RejectedExecutionHandler
//创建持有足够线程的线程池支持给定的并行度, 并通过使用多个队列减少竞争
Executors.newWorkStealingPool
//maximumPoolSize 最大可以至Integer. MAX_VALUE, 是高度可伸缩的线程池,
Executors.newCachedThreadPool
//最大卫Integer.max_value 不回收线程相比Timer , Schedu l edExecutorService 更安全,
功能更强大, 与newCachedThreadPool 的区别是不回收工作线程。
Executors.newScheduledThreadPool
//建个单线程的线程池,相当于单线程串行执行所有任务, 保证接任务的提交顺序依次执行。
Executors.newSingleThreadExecutor
//输入的参数即是固定线程数,既是核心线程
数也是最大线程数, 不存在空闲线程,所以keepAliveTime 等于O
Executors.newFixedThreadPool
我们是不是可以自己弄呢?
1.明确为何需要线程池?
池化技术,防止高并发时,资源上面的耗尽和重复创建。限流保护机制
2. 线程池的如何设计呢?
线程执行时,run方法完成就会结束,如何创建线程不销毁呢?
基本上就是按着这个进行的
第一题 问:如何实现一个订阅发布呢?
答:阻塞队列,原理:notify/wait
第二题:怎么算是线程执行完成呢?
答:run 方法运行完成
第三:线程的状态有几种。两种等待的区别
答:共六种, new ,runabled,waitting ,TIMED_WAITING.block ,terminated. ==> waitting 需要唤醒,属于被动唤醒,TIMED_WAITING超时等待==>约定时间之后,自己会自动唤醒,属于主动唤醒
第四:什么是可重入锁
答:就是方法调用方法,递归,可以共用同一把锁,不会发生死锁,ReentrantLock 和synchronized 都属于可重入锁
第五:synchronized 和 ReentrantLock 的区别
答:
都属于可重入锁。
synchronized 属于关键字, ReentrantLock 属于对象。