创建一个线程
在一个单独的线程中运行一个任务
将执行任务的代码放入run方法中,类要实现Runnable接口,使用lambda表达式创建实例
从这个Runnable构造一个Tread实例,然后启动线程
Runnable r=()->{
...
}
var t=new Thread(r);
t.start();
线程状态
New(新建)、Runnable(可运行)、Blocked(阻塞)、Waiting(等待)、Timed Waiting(计时等待)、Terminated(终止)
新建线程
使用new操作符新建一个新线程,但还没开始运行
可运行线程
调用了start方法的线程就处于可运行状态,可能正在运行也可能没有运行,也不一定始终保持运行
线程调度的细节依赖于操作系统提供的服务
所有桌面操作系统以及服务器操作系统都使用抢占式调度,手机等小型设备使用协作式调度
阻塞和等待线程
不运行任何代码,且消耗最少的资源
当一个线程试图获取内部的对象锁,而这个锁被其他线程占有,进入阻塞状态
当等待另一个线程通知调度器出现一个条件,进入等待状态
当调用有超时参数的方法时,进入计时等待状态
由线程调度器重新激活
当一个线程阻塞或等待时,调度另一个线程运行,当一个线程重新激活时,检查它的优先级
终止线程
当run方法正常退出,线程自然终止
当没有捕获异常而终止run方法,线程意外终止
线程属性
中断线程
没有办法可以强制线程终止
使用interrupt方法请求终止一个线程,并将线程设置为中断状态
中断解释为一个终止请求,被中断的线程可以决定如何响应中断
调用静态Tread.currentTread方法获取当前线程,然后调用isIerrupted方法检查中断状态
t.interrupt();
while(!Tread.currentTread().isIerrupted()) {
...}
如果线程被阻塞,就无法检查中断状态
当在一个被sleep或wait调用阻塞的线程上调用interrupt方法时,这些阻塞调用将被异常中断
当在一个被设置为中断状态的线程上调用sleep方法时,不会休眠,而是清除中断状态并抛出异常
因此在循环中调用了sleep方法,应当捕获异常,而不是检测中断状态
try
{
while(...){
Tread.sleep(delay);}
}
catch(InterruptedException){
...}
使用静态interrupted方法检查当前线程是否被中断,并清除中断状态
守护线程
调用setDaemon方法将一个线程转换为守护线程,用于为其他线程提供服务
当只剩下守护线程时,虚拟机就会退出
线程名
调用setName方法将字符串设置为线程的名字
可用于线程转储
未捕获异常的处理器
线程的run方法不能抛出任何检查型异常,非检查型异常可能会导致线程终止
对于可以传播的异常,没有任何catch子句,线程死亡之前,异常会传递到一个用于处理未捕获异常的处理器
处理器必须实现Tread.UncaughtExceptionHandler接口,这个接口只有一个uncaughtException方法
使用静态Tread.setUncaughtExceptionHandler方法安装一个处理器
使用静态Tread.setDefaultUncaughtExceptionHandler方法为所有线程安装一个默认处理器
如果没有安装默认处理器,则默认处理器为null,如果没有为单个线程安装处理器,那么处理器就是该线程的TreadGroup对象
线程组TreadGroup是一起管理线的程的集合,默认创建的所有线程都属于一个线程组
TreadGroup类实现了Tread.UncaughtExceptionHandler接口,它的uncaughtException方法,对有父线程组的调用父线程组的uncaughtException方法,否则调用它的非null默认处理器,否则如果Throwable是TreadDeath的实例,则什么都不做,否则将线程名以及堆栈轨迹输出到System.err
线程优先级
在java中,每个线程有一个优先级
默认一个线程会继承构造它的那个线程的优先级
调用setPriority方法设置优先级
线程的优先级极度依赖于系统,Java线程的优先级会映射到宿主机平台的优先级
在使用操作系统线程的Java版本中,不要使用线程优先级
同步
竞态条件
取决于线程访问数据的次序,可能导致对象被破坏,这称为竞态条件
在多核处理器上,或在负载很重的机器上运行大量线程,或穿插使用大量IO语句,出现破坏的风险很大
锁对象
使用ReentrantLock类保护代码块,确保任何时刻只有一个线程进入临界区,保证串行化访问
在类中创建一个静态重入锁,使多个线程共享这个锁
private var mylock = new ReentrantLock();
调用lock方法加锁,在finall子句中调用unlock方法解锁
mylock.lock();
try{
...}
finally
{
mylock.unlock();
}
使用重入锁可以使线程反复获得已拥有的锁,被一个锁保护的代码可以调用另一个使用相同锁的方法
有一个持有计数器来跟踪对lock方法的嵌套调用,当持有计数为0时,线程释放锁
另外可以通过构造器构造一个公平锁,倾向于等待时间长的线程,但影响性能
条件对象
使用条件对象管理已经获得锁,但不能通过条件测试的线程
调用newCondition方法获得一个条件对象,一个锁对象可以关联多个条件对象
class Bank
{
private Condition sufficientFunds;
public Bank(){
sufficientFunds = bankLock.newCondition();}
}
调用await方法暂停当前线程并放弃锁,线程进入这个条件的等待集
在任意一个线程中调用signalAll方法,激活等待这个条件的全部线程,否则进入死锁。只要一个对象的状态有变化,就调用signalAll方法
while(...)sufficientFunds.await();
...
sufficientFunds.signalAll();
使用signal方法随机选择等待集中的一个线程激活,并解除阻塞