龙空技术网

Java高并发编程-剖析线程启动的start()方法

从程序员到架构师 67

前言:

如今你们对“java线程start”都比较关心,你们都需要学习一些“java线程start”的相关文章。那么小编也在网上搜集了一些有关“java线程start””的相关文章,希望同学们能喜欢,小伙伴们一起来了解一下吧!

这篇文章,带着大家来分析一下线程启动的start()方法,并且简单地讲解一下模板设计模式在线程类中的使用。

在之前分析中,我们知道线程从NEW状态进入到RUNNABLE装需要调用start()方法,但是当线程调用完start()方法之后到底进行了那些操作呢?在之前的例子中,我们将业务逻辑代码写到了run()方法中,但是实际上执行线程调用的时候却是执行的是线程的start()方法,两者之间又有什么联系呢?

下面我们就来分析一下。

Thread start方法源码分析

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) {                          }        }    }

通过源码可以看出来,源码逻辑相对比较简单,其中最主要的一个代码就是start0()方法。在源码中可以看到start0(),代码如下所示。

  private native void start0();

也就是说,在我们调用了start()方法之后,会调用start0()方法,还是没有发现run()方法是什么时候被执行的,但是查看JDK的文档之后,我们会知道,其实这里的start0()方法其实是通过JNI技术进行调用的,而run()方法也是被JNI的start0()方法。阅读源码之后会有如下的一些问题。

当我们new一个线程之后,threadStatus 属性的值应该是默认为0的,当不等于0的时候就会抛出异常。这个在源码中说得比较清楚。threadStatus为零表示这个当前状态是NEW根据threadStatus的状态可以知道,不能进行两次启动线程操作,也就是说同一个线程对象,不能两次重复调用start()方法,否则就会抛出异常。线程启动之后就会被加入到一个线程组中,后续会说到线程组相关的概念。当一个线程结束,进入到TERMINATED状态之后,是没有办法通过调用start()方法再次进入到RUNNABLE状态或者是RUNNING状态的。

模板设计模式在Thread中的使用

通过上面的分析,可以知道,一个线程真正的逻辑执行代码是被放到run()方法中,通常情况下run()方法就是我们编写线程执行逻辑的地方,这也就是为什么我们要重写run()方法,用start()启动的线程。Thread类中的run方法如下。

    @Override    public void run() {        if (target != null) {            target.run();        }    }

默认情况下,这个方法可以看做是一个空方法。方法体中没有任何代码。

不难发现在Thread中的run()方法start()方法就是一个非常典型的模板设计模式,父类编写算法结构代码,子类负责按照对应的模板进行实现。下面来看一个小例子

模板设计模式小例子

首先创建一个消息输出的接口类,这个类就是制定消息输出的规则,代码如下

public interface PrintMessage {    abstract void print();}

接下来就是其子类要实现这个消息输出消息了。代码如下

public class Repoter implements PrintMessage {        public  void printNews() {        print();    }    @Override    public void print() {        System.out.println("show news");    }}

编写测试类main方法。

public class TemplateMain {    public static void main(String[] args) {        Repoter repoter = new Repoter(){            @Override            public void print() {                System.out.println("这个是一个设计模板方法");            }        };        repoter.printNews();    }}

这里会看到整个的模式都是按照Thread的模式来进行。其中的print()方法和printNews()方法就类似于Thread类的run()方法和start()方法。这样一来,父类只需要告诉子类需要做的事情是什么,而不需要知道至于子类如何实现,最终就是子类的事情了。

简单的模拟一个叫号系统

很多人都有过存款或者买票的体验,他们有一个共同的特点就是需要排队叫号。其实这种机制解决了很多的问题。例如可以做到限流,可以合理的分配处理人员的位置,可以很好的解决了由于排队引起的社会矛盾。同时也减轻了业务员、管理人员的压力

我们这里简单的模拟有四个窗口的叫号系统。四个窗口就相当于有四个线程。通过这四个窗口来模拟50人排队的场景。

public class TicketWindow extends Thread {    private final String name;    private static final int MAX = 50;    private int index = 1;    public TicketWindow(String name) {        this.name = name;    }    @Override    public void run() {        while (index<MAX){            System.out.println(name+" 号柜台,出号 "+index++);        }    }    public static void main(String[] args){        TicketWindow ticketWindow1 = new TicketWindow("一");        ticketWindow1.start();        TicketWindow ticketWindow2 = new TicketWindow("二");        ticketWindow2.start();        TicketWindow ticketWindow3 = new TicketWindow("三");        ticketWindow3.start();        TicketWindow ticketWindow4 = new TicketWindow("四");        ticketWindow4.start();    }}

运行上面代码之后会看到如下的结果,简单地分析之后,会发现其实这是有问题的。1号票被多个柜台出了。

为什么会出现这样的问题,这就是每个线程执行的逻辑都不一样,新建了四个线程,每个线程的票号都是从0~50。四个线程并没有操作同一个票号排序。所以就会出现上面这个问题。那么我们如何去解决这个问题呢?我们想到了使用static 去修饰 index变量的方式。代码如下。

public class TicketWindow extends Thread {    private final String name;    private static final int MAX = 50;    private static int index = 1;    public TicketWindow(String name) {        this.name = name;    }    @Override    public void run() {        while (index<MAX){            System.out.println(name+" 号柜台,出号 "+index++);        }    }    public static void main(String[] args){        TicketWindow ticketWindow1 = new TicketWindow("一");        ticketWindow1.start();        TicketWindow ticketWindow2 = new TicketWindow("二");        ticketWindow2.start();        TicketWindow ticketWindow3 = new TicketWindow("三");        ticketWindow3.start();        TicketWindow ticketWindow4 = new TicketWindow("四");        ticketWindow4.start();    }}

经过修改之后,运行结果如下,这个时候看上去一切都就正常了。

通过对index进行static的修饰,做到了多线程共享索引的操作。看上去是满足了需求,但是实际上还是有很多的问题,如果共享索引的数据太大的时候,也会出现线程安全的问题。这个问题在后续的分享中会提到。所以说static只能解决小问题,需要解决大问题的话还需要大手笔的解决方案。在后续的分享中也会提到。这里就先到这里,大家可以先消化一下Thread 的模板设计模式原理。

标签: #java线程start