一、线程的创建方式
1、继承Thread类(不推荐):只能被单继承,不推荐使用
2、实现Runable的接口(推荐):不需要返回值时,推荐使用
3、实现Callable接口(JDK1.5): 核心方法叫call()方法(相当于run()),有返回值,推荐使用
4、线程池:通过Executor 的工具类可以创建三种类型的普通线程池:固定大小的线程池、单线程池、缓存线程池
问题:线程调用start方法和调用run方法的区别?
a) 直接调用run方法相当于在当前线程执行run方法的方法体(同步,注意 :run方法不是手动调用的)
b) 调用start方法才会在内存中开启一个线程,和当前线程挣夺CPU时间片
二、线程的生命周期
创建----》就绪----》运行----》阻塞—》死亡
注意:有时线程直接从 运行—》死亡,不经过阻塞,所以阻塞状态不一定执行
三、线程安全
什么是线程安全问题?- 当多个线程访问共享资源时,因为线程调度不确定性,导致该资源最后的属性状态不一致的问题,就称之为线程安全问题。
为什么会有线程安全的问题发生?
1)可见性
2)原子性
3)有序性
当这个3个特性有一个特性没有得到保证时,就会发生线程安全的问题
什么是可见性?
可见性:任意一个线程对内存数据的修改,对其他的线程是立即可见的
/**
* 打印1 - 正常情况99.99%
* 打印0 - 概率很低
* 可能会循环很长一段时间
*/
public class MainTest3 {
private static int i = 0;
private static boolean flag = true;
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
while(flag){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i);
}
}.start();
i=1;
flag=false;
}
}
怎么保证可见性:
1、使用同步关键字synchronized(特性:当某个线程释放锁资源时,会强制将工作内存中的数据刷新到主存中)
2、使用volatile关键字,被该关键字修饰的属性具有可见性的特点
什么是原子性?
原子性:某一步操作,是一个原子的不可拆分的操作
public class MainTest3 {
private static int x = 0;
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(){
@Override
public void run() {
x++;
}
}.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("x=" + x);
}
}
i++;//非原子
i=0;//原子
怎么保证原子性:
1、使用同步关键字synchronized,因为加锁了之后,其他线程就不能执行这个代码,所有代码从头到尾不会被打断
2、使用JDK1.x之后提供的一些具有原子性的工具类(AtomicXxxxxxx)
什么是有序性?
有序性:就是保证代码的执行顺序
CPU的指令重排:cpu在执行一段代码时,从优化的角度考虑,有可能会打乱代码的执行顺序。
指令重排的保证:在单线程情况下,重排后的代码执行的结果一定和重排前的代码执行结果一致。
//共享资源
flag = true;
obj = null;
//线程1
while(flag);
obj.method();
//线程2
obj = new Object();
flag = false;
怎么保证有序性?
1、保证代码的原子性,可以杜绝指令重排所带来的负面效果
2、使用volatile关键字,可以保证属性的局部有序
线程安全的懒汉式单例模式
/**
* 懒汉式 - 线程安全版
*/
public class SingleClass {
//私有的静态变量,用来保存当前的单例对象
private volatile static SingleClass singleClass;
//私有化构造方法
private SingleClass(){}
//公有的静态方法,获得单例对象
//1
//2
public static SingleClass getInstance(){
//2
if(singleClass == null){
synchronized(SingleClass.class){
if(singleClass == null) {
//1 - 申请堆内存地址
//2 - 初始化内存
//3 - 将变量指向该内存地址
singleClass = new SingleClass();
}
}
}
return singleClass;
}
}
synchronized关键字:
作用范围:代码块/方法(非静态方法、静态方法)
synchronized(同步锁对象){
}
同步锁对象通常用来锁住多个线程中公有的对象,具体锁什么需要根据实际的业务决定。
非静态同步方法默认锁的是this
静态同步方法默认锁的是当前类的Class对象
同步关键字的优化:提高锁的细粒度(降低锁的范围)
四、线程间的通讯
什么是线程间通讯?- 在实际开发时,往往会碰到多线程模型,可能某个线程需要拿到另一个线程的计算数据,但是因为线程调度的不确定性,所以需要通过线程间通讯的技术实现线程之间的数据交换
线程间通讯的方案:
1、wait()/notify()方法
2、阻塞队列
五、线程池
JDK提供了ThreadPoolExecutor对象实现线程池:
形参介绍:
int corePoolSize - 核心线程数的大小
int maximumPoolSize - 最大线程数的大小
long keepAliveTime - 线程的空闲存活时间
TimeUnit unit - 线程存活时间的单位
BlockingQueue<Runnable> workQueue - 阻塞队列的实现
ThreadFactory threadFactory - 线程池中线程的创建方式
RejectedExecutionHandler handler - 当阻塞队列已满,新的任务的拒绝策略(四种)
六、线程的运用场景
MQ消费消息时是一个同步的过程,所以可以通过线程池并发消费提高消费的速率。
希望能帮助到你,你的点赞是我最大的动力,嘻嘻