线程基础
介绍多线程中的基础知识,包含线程创建、线程运行状态和线程中断
线程创建
线程创建有且/只有一种方式,继承Thread类,并重写Run()方法
class TaskDemoKt: Thread() {
override fun run() {
super.run()
println( currentThread().name + "执行任务")
}
}
fun main(){
val demoKt = TaskDemoKt()
demoKt.start()
}
在Thread内部是通过本地方法的方式来进行实现的
- Thread
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
//其他步骤省略...
this.target = target;
}
//run()方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
//本地方法
private native void start0();
从这里可以看到在创建Thread(Runnable)对象时,会将Runnable对象作为线程内部的一个成员变量target
这个target在两个地方会使用到,第一个是直接执行run()方法时,第二个是本地native start0();
下面分析JVM是如何执行start0()这个本地方法的
- start0
//1. 调用操作系统创建Thread
bool os::create_thread(Thread* thread, ThreadType thr_type,
size_t req_stack_size) {
assert(thread->osthread() == nullptr, "caller responsible");
// Allocate the OSThread object
OSThread* osthread = new OSThread();
if (osthread == nullptr) {
return false;
}
}
//2. 父Thread启动子Thread
void os::start_thread(Thread* thread) {
OSThread* osthread = thread->osthread();
osthread->set_state(RUNNABLE);
pd_start_thread(thread);
}
实现任务的几种方式
通过实现Runnable接口、创建线程池、Timer类等方式都可以把业务逻辑代码包装成为一个task,在使用Thread来进行执行;
实现Runnable
创建线程池
创建线程池,以创建同步队列的线程池为例
- 创建线程池
fun main() {
//以创建同步队列的线程池为例
val poolExecutor = ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, LinkedBlockingDeque())
IntRange(0, 10).forEach { _ -> poolExecutor.submit { println("打印当前时间:" + LocalDateTime.now()) } }
}
//ThreadPoolExecutor中的线程的创建过程
//execute()
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//Worker(firstTask)
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
使用Timer
- 使用Timer
fun main() {
val timer = Timer()
timer.schedule(timerTask { println("开始打印:" + LocalDateTime.now()) }, 1000, 1000)
}
- Timer.java
//实现Timer执行的方法
private void sched(TimerTask task, long time, long period) {
queue.add(task);
}
//创建TimerThread
private final TimerThread thread = new TimerThread(queue);
//TimerThread
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
//mainLoop
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
task = queue.getMin();
task.run();
}
}
}
以上这是Timer执行过程的伪代码,主要是分为以下几个部分
- 构建Timer是初始化TimerThread/queue
- TimerThread循环处理queue中的task
小结
可以看到以上三种方式不管是实现Ruannble接口/创建线程池/使用Timer在底层都是通过创建一个Thread对象来执行的.
下面来分析一下Thread的方法和实现
线程任务
线程能执行的任务分为Runnable,Callable,ForkJoinTask三类,下面来详细的介绍一下
Runnable
实现Runnable接口
@Override
public void run(){
//TODO 执行代码
}
Callable
实现Callable
new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
}
ForkJoinTask
继承ForkJoinTask接口
new ForkJoinTask<Object>() {
@Override
public Object getRawResult() {
return null;
}
@Override
protected void setRawResult(Object value) {
}
@Override
protected boolean exec() {
return false;
}
}
通过实现Runnable接口的方式来执行任务比直接创建线程要好的多,这样可以复用线程。
线程状态
线程的状态可以分为
- New(新创建)
- Runnable(可运行)
- Blocked(可阻塞)
- Waiting(等待)
- Timed Waiting(计时等待)
- Terminated(被终止)
线程的创建/启动/执行是由JVM的native方法调用操作系统的API进行执行
中断线程
在java中不提供强制停止线程的方式,因为线程的强制停止可能会造成数据异常或者业务中断,这是业务方和中断方都不愿意看到的方式。java提供的方式是希望线程之间可以相互的通知、相互协助。提供这个方式的是interrupt。
while(!Thread.currentThread().isInterrupted()){
//more work to do
}
判断线程条件和业务规则都满足时,才会继续执行。
在线程sleep期间,线程可以感受到中断信号,感受到中断信号后会清除中断位标志并且抛出InterruptedException异常。
关于线程中断isInterrupted()和线程关闭stop()的区别在于
线程中断是不会中断线程的运行的,而线程关闭stop会关闭线程,stop()是不安全的说法来自于stop()执行后会立即释放该线程所持有的所有锁(包括synchronized持有的全局对象锁)
volatile不能作为线程标记位
volatile作为并发标记位的时候,当用来使线程阻塞的时候,其他线程仍旧可以改变标记位的值,但是由于持有线程不是活动状态,不能及时获取到volatile标记对象改变后的值,因此volatile不适合作为线程并发安全的标记位。根本原因在于volatile标记的对象不是全局共享的,在内存一致性的协议下作做不到全局可见性;
wait/notify/notifyAll()方法使用注意
- wait()方法,必须在synchronized中持有锁后才能使用,因为必须持有对象的monitor锁,这样才能防止在执行前后线程被切换。
- sleep()被定义在Thread中,wait/notify/notifyAll都要操作monitor,属于对象级的锁。
- wait/notify与sleep的区别
- 相同点
都让线程阻塞、都可响应InterruptException异常 - 不同点
2.1 sleep不会释放monitor锁
2.2 sleep方法必须定义一个等待时间,到期后可主动恢复,wait方法没时间参数意味着一直会等待下去
2.3 wait/notify是Object的,sleep是Thread的方法
2.4 wait必须在synchronized中使用
wait必须在synchronized中使用的原因是因为防止指令的乱序执行导致wait()后的代码提前执行,使用synchronized来保证原子性
- 相同点