Java多线程系列 基础篇02 线程的创建和运行

1.线程创建的方式常用有两种

1. 继承 Thread 类创建线程
2. 实现 Runnable 接口创建线程

2.Thread 和 Runnable的区别

Thread和Runnable的相同点:都是"多线程的实现方式”。
Thread和Runnable的不同点:Thread 是类,而Runnable是接口;Thread本身是实现了 Runnable 接口的类。我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此Runnable具有更好的扩展性。此外,Runnable还可以用于“资源的共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。通常,建议通过“Runnable”实现多线程!

3.Thread和Runnable的多线程示例(暂不考虑数据竞态)

继承Thread创建步骤
1. 定义Thread类的子类,并重写该类的run()方法,该方法为线程执行体
2. 创建Thread子类的实例,也就是创建了线程对象
3. 启动线程,通过调用线程对象的start()方法来启动线程
Thread创建代码
package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description: 线程创建
 * @Author: lizhouwei
 * @CreateDate: 2018/5/20 16:17
 * @Modify by:
 * @ModifyDate:
 */
class MyThread extends Thread {
    private AtomicInteger ticket = new AtomicInteger(10);

    @Override
    public void run() {
        for (int i = 1; i <= ticket.intValue(); i++) {
            System.out.println(Thread.currentThread().getName() + " ticket " + i);
        }
    }
}

public class ThreadDmo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.start();
    }
}

实现Runnable创建步骤
1. 定义Runnable接口的实现类,重写run()方法,该方法为线程执行体
2. 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3. 启动线程,通过调用线程对象的start()方法来启动线程
Runnable创建代码
 package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description: 线程创建
 * @Author: lizhouwei
 * @CreateDate: 2018/5/20 16:17
 * @Modify by:
 * @ModifyDate:
 */
class MyRunnable implements Runnable {
    private AtomicInteger ticket = new AtomicInteger(10);

    public void run() {
        for (int i = 1; i <= ticket.intValue(); i++) {
            System.out.println(Thread.currentThread().getName() + " ticket " + i);
        }
    }
}

public class RunnableDmo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread myThread = new Thread(myRunnable);
        myThread.start();
    }
}

4. start() 和 run()的区别

start():启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用(会抛出异常)。
run()  :run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程! 

2. start() 源码(by jdk1.8)

public synchronized void start() {
         
         //判断状态是否为 New ;
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        // 将线程添加到ThreadGroup中
        group.add(this);

        boolean started = false;
        try {
            // 通过start0()启动线程
            start0();
            // 设置started标记
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

5. 通过Thread的实例对象调用start()方法到底是怎么启动线程的?下面对其实现方式做一些简单的补充。

  1. 基于Kernel Thread(KLT)的映射来实现:KLT是内核线程,内核线程由OS直接完成调度切换,它相对应用程序的线程来讲只是一个接口,外部程序会使用一种轻量级进程(Light Weight Process,LWP)来与KLT进行一对一的接口调用。也就是说,进程内部会尝试利用OS的内核线程去参与实际的调度,而自己使用API调用作为中间桥梁与自己的程序进行交互。
  2. 基于用户线程(User Thread,UT)的实现:这种方式是考虑是否可以没有中间这一层映射,自己的线程直接由CPU来调度,或许理论上效率会更高。不过这样实现时,用户进程所需要关注的抽象层次会更低一些,跳过OS更加接近CPU,即自己要去做许多OS做的事情,自然的OS的调度算法、创建、销毁、上下文切换、挂起等都要自己来搞定(因为CPU只做计算)。这样做显然很麻烦,许多人曾经尝试过,后来放弃了。
  3. 混合实现方式:它的设计理念是希望保留Kernel线程原有架构,又想使用用户线程,轻量级进程依然与Kernel线程一一对应保持不变,唯一变化的就是轻量级进程不再与进程直接挂钩,而是与用户线程挂钩,用户线程并不一定必须与轻量级进程一一对应,而是多对多,就像在使用一个轻量级进程列表一样,这样增加了一层来解除轻量级进程与原进程之间的耦合,可能会使得调度更为灵活。

在以前的JDK版本中,尝试使用UT的方式来实现,但后来放弃了,采用了与Kernel线程对应的方式,至于一些细节,与具体的平台有很大的关系,JVM会适当考虑具体平台的因素去实现,在JVM规范中也没规定过必须如何去实现,所以对于程序员来讲,只需要知道在new Thread(),调用start()方法后,理论上就有一个可以被OS调度的线程了。

posted @ 2018-05-20 15:46  lizhouwei  阅读(131)  评论(0编辑  收藏  举报