目录
线程的创建方式
线程的生命周期
线程同步的方法
多线程内存可见性
线程安全问题
线程的创建方式
-
继承Therad类
定义一个类继承Therad类
重写run()方法(线程实际执行的逻辑)
创建类的对象,调用start()方法开启线程
-
实现Runnable接口
定义一个类实现Runnable接口,并且实例化
重写run()方法
创建一个Therad对象,将实现Runnable接口的类作为实参
调用start()方法开启线程
-
相较于继承Therad类
-
在多个线程执行相同逻辑时,继承Thread类需要为每个线程创建一个Therad线程,重复写执行的代码
-
而实现Runnable接口只需将实现接口的类实例化,作为参数传个Therad类
-
-
实现Callable接口和Future接口
创建一个类实现Callable接口,接口泛型表示返回值的类型
重写call()方法
创建FutureTask对象用于管理返回值(参数为实现Callable接口的实例化对象)
创建Thread对象用于启动线程(参数为futuerTask对象)
-
利用线程池创建
线程池的七个参数
-
核心线程数,即使没有任务,也会有的线程数量
-
最大线程数,当任务队列已满且新任务提交时,会创建新线程直到最大值
-
空闲时间,当线程数量超过核心线程数时,多余的空闲线程被终止的时间
-
空闲时间单位
-
阻塞队列
当队列为空时,获取元素的线程会被阻塞,直到有元素
当队列已满时,插入元素操作的线程会被阻塞,直到队列中有空间
-
线程工厂(创建线程的方式)
线程工厂(ThreadFactory)是一个用于创建线程的工具接口,接口只有一个方法,接受一个Runnable对象作为参数,表示线程要执行的任务,并返回创建一个Thread对象
-
拒绝策略,当任务队列满且线程数量超过最大线程数时,多余任务的处理策略
终止策略(AbortPolicy)任务无法提交,直接抛出异常
调用者运行策略(CallerRunsPolicy)由提交这个任务的线程去执行这个任务
丢弃策略(DiscardPolicy)直接丢弃任务,不抛出异常
丢弃最旧策略(DiscardOldestPolicy)丢弃队列中最旧的未处理的任务,将新任务加入队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
-
线程的生命周期
-
线程是程序执行的最小单位,是进程中的一个执行路径
-
多线程是指在一个进程中执行多个线程,每个线程可以独立执行不同的任务
-
新建,创建Thread对象时,线程处于新建阶段
-
就绪,调用start()方法,线程处于就绪状态,等待CPU分配时间切片(操作系统分配给可执行线程的处理器时间)
-
运行,拿到CPU分配的时间切片,执行run()方法里面的代码
-
阻塞,当其他线程拿到锁执行代码时,线程处于阻塞状态
-
等待,调用wait()方法,线程处于等待状态,等待其他线程调用notify()方法唤醒
-
超时等待,当调用sleep(),wait()方法,参数是超时时间,线程进入超时等待状态,如果长时间没有被唤醒,线程会被自动恢复执行
-
终止,执行完run()方法里面的代码时,或出现异常,线程会被终止
线程同步的方法
-
synchronized关键字
修饰方法时,表示这个方法被加锁,只能由一个线程去执行,其他线程等待这个线程执行完毕释放锁,才能由下一个线程执行
-
ReentrantLock类
提供比synchronized更方便的方法,但是需要手动上锁(lock)和释放锁(unlock)
可以调用tryLock()方法尝试获取锁,如果获取不到就直接返回,不去排队阻塞。
可以去中断等待的线程,当线程等待锁的过程中被其他线程中断时,会直接中断并抛出异常
-
采用公平锁,获取锁的顺序按照线程的请求顺序
-
等待时间可以预测,大致就是上一个线程所运行的时间
-
-
CountDownLatch类
主要用于一个或多个线程等待其他线程
初始化一个计数器
当线程执行完代码后调用countDown()方法减少计数器的值
其他线程会执行awit()方法进入阻塞状态,直到计数器的值为零
将所有的等待线程释放
-
信号量(Semaphore)
-
用于控制同时访问特定资源的线程数量
创建一个信号量,设置许可数量
当线程访问共享资源的时候,会先获取信号量,如果许可数大于0,则可以去访问共享资源,并且许可数减一
当线程完成对共享资源的操作后,会释放资源,许可数加一。
-
多线程内存可见性
-
在多线程环境下,线程之间可能存在缓存,当一个线程对公共变量修改时,其他线程不能马上看到这个修改的值。
-
volatile 关键字
volatile关键字修饰的变量可以确保修改后其他线程立即可见
当变量被修改时,会将修改后的值放入主内存中,其他线程缓存中的变量会失效,当使用变量时,必须从主内存中获取
-
使用synchronized关键字或Lock锁
当线程进入同步代码块或方法中时,会获得一个锁,强制该线程从主内存中获取共享变量,当线程释放锁时,会将修改后的共享变量写回主内存
-
使用原子类
在多线程环境下对单个数据进行原子化操作
-
底层处理器会提供原子指令,如CAS指令,当执行CAS指令时,会在内存上发出一个信号,表示内存位置值正在被修改,将其他线程中的缓存副本标记无效,当其他使用共享变量时,从主内存中重新获取变量
CAS主要有三个参数,内存位置(V)、期望原值(A)、新值(B)
-
先读取内存位置中的值(V),作为期望原值(A)
-
通过计算得到新值(B)
-
再次读取内存位置中的值,与期望原值进行比较
-
如果相同,说明值没有被其他线程修改,更新内存位置中的值为计算出来的新值(B)
如果不同,说明值被修改了,重写读取,直到尝试读取一点的次数或比较相同为止
-
-
内置屏障强制处理器按指定的顺序执行内存操作,并保证不同线程对内存的修改能被其他线程看到(缓存中的变量失效)
-
使用乐观锁,当CAS指令出现ABA问题时
如何一个值从A变为B,在从B变回A,CAS认为这个变量没有被修改过
这时使用乐观锁的版本号机制,避免ABA问题
-
线程安全问题
-
线程之间抢占执行和随机调度
-
线程的执行顺序是随机的
使用synchronized关键字或Lock锁,保证方法和同步代码块由一个线程执行时上锁,其他线程等待
使用原子类,对数据类型或对象进行原子操作,确保不受线程抢占调度的影响
-
-
多个线程同时修改同一变量
使用synchronized关键字或Lock锁
-
死锁问题
-
每个线程在持有资源的同时,还在等待另一个线程释放资源
避免嵌套锁,一个线程拥有一个锁的同时避免再去获取另一个锁。或者采用固定的顺序
-
-
内存可见性
-
在多线程环境下,一个线程对共享变量修改时,其他线程不能马上看到修改后的变量
使用volatile关键字,在一个线程修改共享变量后,将修改后的放入主内存中,其他线程中的缓存失效,必须去主内存中获取
-
-
指令重排序
-
一个线程在执行过程中可能先去执行别的指令,再执行写入操作。如果一个线程里面的数据会依赖另一个线程的数据结果,当CPU调度时,可能第一个线程先执行完,获得的数据不正确(获得初始化的值)
使用volatile关键字,被修饰的变量在编译器执行的时候限制指令重排序,会按照变量的读写顺序执行
-