1、创建线程的方式

线程创建方式是:继承 Thread 类,重写 run 方法。如下:

public class Task extends Thread{
    @Override
    public void run() {
        System.out.println("线程执行了");
    }
}

客户端使用如下,使用线程对象的start方法启动线程:

public class Client {
    public static void main(String[] args) {
        System.out.println("主线程开始执行");
        Task task = new Task();
        task.start();
        System.out.println("主线程执行结束");
    }
}

输出结果:

主线程开始执行
主线程执行结束
线程执行了

注意:

1、必须使用 start 方法启动线程,而不能使用run方法启动,使用run方法和普通的方法调用没啥区别,使用run方法调用操作系统不会创建线程。(start 和 run 方法的区别,其实是模板设计模式中的细节,如果了解模板设计模式,那么你就会恍然大悟)

2、一个线程对象只能启动一次。否则会报 IllegalThreadStateException 异常,这是因为线程有状态,启动线程之后,状态会更改,再次启动则会出现错误,源码如下:

    public synchronized void start() {
        /**
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

实际上新创建的线程状态是 NEW 状态,启动之后便不是该状态,而 start 方法只能启动状态是 NEW 的线程。

2、其他方式创建线程

这里说的其他方式,其实还是使用上面的Thread类,只是使用方式有所不同,我们可以使用内部类的方式。

public class Client {
    public static void main(String[] args) {
        System.out.println("主线程开始执行");
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("线程执行了...");
            }
        };
        thread.start();
        System.out.println("主线程执行结束");
    }
}

当然还可以使用匿名内部类。

public class Client {
    public static void main(String[] args) {
        System.out.println("主线程开始执行");
        new Thread() {
            @Override
            public void run() {
                System.out.println("线程执行了...");
            }
        }.start();
        System.out.println("主线程执行结束");
    }
}

3、线程的生命周期

 public enum State {
        /**
         * 线程还未启动,只存在于线程刚创建,未start之前
         */
        NEW,

        /**
         * 可运行的状态,这个状态的线程,已经在虚拟机中执行了,但是这个"执行",不一定是真的在运行, 也有可能是在等待CPU资源
         */
        RUNNABLE,

        /**
         * 阻塞状态,一般是等待其他线程的锁释放
         */
        BLOCKED,

        /**
         * 等待状态
         */
        WAITING,

        /**
         * 这个状态和WAITING状态的区别就是,这个状态的等待是有一定时效的,
         * 即可以理解为WAITING状态等待的时间是永久的,即必须等到某个条件符合才能继续往下走,否则线程不会被唤醒。
         * 但是TIMED_WAITING,等待一段时间之后,会唤醒线程去重新获取锁。
         */
        TIMED_WAITING,

        /**
         * 线程执行结束之后的状态
         */
        TERMINATED;
    }

上面简单说明了线程的状态,详细说明后续章节再讨论。

4、模板设计模式

定义一个操作算法中的框架,而将这些步骤延迟加载到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

主要的实现方式是: 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现,达到各子类中公共的行为被提取到一个公共父类中以避免代码重复、又可以变成子类达到不同效果的目的。

一般实现方式是:

  • 新建一个抽象类。
  • 编写模板方法,也就是统一调用的入口方法,该方法将会执行所有的流程。注意模板方法一定要使用final关键字修饰,使得子类无法重写,就不会修改模板的执行流程。
  • 模板方法中的大部分方法是该类已经实现的,少数方法的实现暂未可知,但是这些方法影响整体的执行流程,那么将这些方法声明为抽象方法。
  • 子类继承模板类,实现抽象方法即可。
public abstract class Template {

    public final void start(){
        method1();
        method2();
        method3();
        method4();
    }

    private void method1(){
        System.out.println("method1 执行了");
    }

    private void method2(){
        System.out.println("method2 执行了");
    }

    private void method4(){
        System.out.println("method4 执行了");
    }

    protected abstract void method3();
}

上面是模板类,按照上面的编写流程来看,十分简单。

public class TemplateOne extends Template{
    @Override
    protected void method3() {
        System.out.println("TemplateOne 执行了 method3");
    }
}

TemplateOne 是模板类的子类之一。

public class TemplateTwo extends Template {
    @Override
    protected void method3() {
        System.out.println("TemplateTwo 执行了 method3");
    }
}

TemplateTwo 是模板类的另一个子类。

客户端代码如下,通过结果我们可以看出,对于不同的子类,由于继承了父类,大部分功能都是一样的,部分父类无法实现的功能由子类去实现,不同的子类就实现了不同的功能。

public class Client {
    public static void main(String[] args) {
        Template template = new TemplateOne();
        template.start();
        System.out.println("########################");
        template = new TemplateTwo();
        template.start();
    }
}

结果如下:

method1 执行了
method2 执行了
TemplateOne 执行了 method3
method4 执行了
########################
method1 执行了
method2 执行了
TemplateTwo 执行了 method3
method4 执行了

5、Thread 类中的模板设计模式

首先看下 Thread 类中的 start 方法。

    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }

    private native void start0();

这里的start方法内容不算多,但是是一个模板方法,里面进行了一系列流程处理,其中调用了 start0 方法,该方法是个本地方法,最终操作系统会调用到 run 方法。暂且把 start0 方法看作 run 方法,那么类比下上面的模板设计模式,是不是非常类似?

  • start 方法是一个模板方法,是一系列操作的入口。(其实最好是使用final修饰)
  • run 方法可以看作是模板中的抽象方法,需要子类实现。
  • 不同的子类重写了run方法处理了不同的事情,就是线程的不同应用,是模板的具体实现。

指尖改变世界
27 声望6 粉丝