线程基础


线程基础

介绍多线程中的基础知识,包含线程创建、线程运行状态和线程中断

线程创建

线程创建有且/只有一种方式,继承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执行过程的伪代码,主要是分为以下几个部分

  1. 构建Timer是初始化TimerThread/queue
  2. 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接口的方式来执行任务比直接创建线程要好的多,这样可以复用线程。

线程状态

线程的状态可以分为

  1. New(新创建)
  2. Runnable(可运行)
  3. Blocked(可阻塞)
  4. Waiting(等待)
  5. Timed Waiting(计时等待)
  6. 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()方法使用注意

  1. wait()方法,必须在synchronized中持有锁后才能使用,因为必须持有对象的monitor锁,这样才能防止在执行前后线程被切换。
  2. sleep()被定义在Thread中,wait/notify/notifyAll都要操作monitor,属于对象级的锁。
  3. wait/notify与sleep的区别
    1. 相同点
      都让线程阻塞、都可响应InterruptException异常
    2. 不同点
      2.1 sleep不会释放monitor锁
      2.2 sleep方法必须定义一个等待时间,到期后可主动恢复,wait方法没时间参数意味着一直会等待下去
      2.3 wait/notify是Object的,sleep是Thread的方法
      2.4 wait必须在synchronized中使用
      wait必须在synchronized中使用的原因是因为防止指令的乱序执行导致wait()后的代码提前执行,使用synchronized来保证原子性

参考文章

如何杀死java中的Thread
Java Thread Primitive Deprecation


  TOC