三、多线程基础 - 线程的创建

一、Thread的run与start方法

class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("hello word");
    }
}

MyThread thread = new MyThread();
thread.start();

 
run方法内为线程所要执行的任务,直接调用thread.run(),只是串行的执行,而没有以多线程的方式来执行。

调用start方法JVM会自动调用run方法,并以多线程的方式来执行,但start方法只能执行一次。

对于start与run方法来说是模板设计模式的一种体现。

 

1、模板设计模式

什么是模板设计模式 ?即由父类负责整体的逻辑结构,而子类则负责逻辑的实现细节,以下是一个简单的示例:

public class Template {

    public final void print(String message){
        System.out.println("=============");
        printTask(message);
        System.out.println("========");
    }

    protected void printTask(String message){
        //你要做的事
    }


    public static void main(String[] args) {
        Template template = new Template() {
            @Override
            protected void printTask(String message) {
                System.out.println("打印信息:" +message);
            }
        };

        template.print("1998-12-17");
    }

}


结果:

=============
打印信息:1998-12-17
========

 
print方法类似于Thread的start方法,而printTask则类似于run方法,这样做的好处是,程序结构由父类控制,并且是final修饰的,不允许被重写,子类只需要实现想要的逻辑任务即可

 
 

2、start与run模板设计模式的体现

2.1 start ()

public synchronized void start() {
    /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
        }
    }
}

执行流程:

在线程开始时,会调用start方法 -----> JNI的satrt0方法 -----> run方法 。

知识要点:

Thread被构造后的NEW(新建)状态,事实上threadStatus这个内部属性为0。

不能两次启动Thread,否则就会出现IllegalThreadStateException异常。

线程启动后将会被加入到一个ThreadGroup中,后文中我们将详细介绍ThreadGroup。

一个线程生命周期结束,也就是到了TERMINATED(终止)状态,再次调用start方法是不允许的,也就是说TERMINATED(终止)状态是没有办法回到RUNNABLE(可运行)状态的。

 

2.2 run()

@Override
public void run() {
    if (target != null) {
        target.run(); //此处的target为传入的是Runnable对象,如果你没有传入那么就是null了
    }    
}

Thread的run和start就是一个比较典型的模板设计模式,父类编写逻辑结构结构代码,子类实现逻辑细节。

 

二、创建线程的方式

创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元(即run方法)则有两种方式。

第一种是重写Thread的run方法,第二种是实现Runnable接口的run方法,并且将Runnable实例用作构造Thread的参数。

 

1、继承Thread类

class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("hello word");
    }
}

MyThread thread = new MyThread();
thread.start();

 

2、实现Runnable接口

Runnable runnable = new Runnable() {
     @Override
     public void run() {
       System.out.println("t2:传入参数的Runnable接口重写方法");
     }
};

Thread t2 = new Thread(runnable);
t2.start();

此处是重写Runnable接口的run方法,将这个Runnale接口的实现类作为参数创建Thread对象。

内部执行流程为:将Runnable接口实现类对象赋值给内部的target属性,target调用run方法。

 

3、两者比较实现Runnable接口的好处

避免了java的单继承的局限性

多个线程可以共享同一个接口实现类的对象,非常适用于处理同一份资源的情况。

 

4、关于线程池与Callable接口

关于网上很多说线程池与(Callable接口、FutureTask)也是创建线程的方式,我自己之前也是这样认为的,不过现在可能有点偏差。

线程池确实可以创建线程,但最终也是以Runnable接口作为参数来创建Thread。

而Callable接口、FutureTask,单单的凭借这两个类,线程都创建不了,还是要依靠Thread类来创建线程。

因此创建线程只有一种方式,而实现线程的执行单元有两种方法:重写Thread的run方法、实现Runnable接口的run方法,并且将Runnable实例用作构造Thread的参数。

 

4.1 线程池创建

对于线程池中线程的创建是通过ThreadFactory的newThread方法来创建的,而Executors中提供了一个默认实现DefaultThreadFactory,代码如下:

private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

通过上面代码发现它会把传递过来的Runnable的参数,作为Thread的参数来创建线程。

 

4.2 Callable与FutureTask

一个单纯的Callable没鸟用,需要配合FutureTask来使用,即便是配合线程池来使用,其内部也是通过创建FutureTask的方式。

image-20211107220208994

我们发现FutureTask实现了Runnable接口,以下是其run方法的实现:

public void run() {
        if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call(); //此处调用的就是你实现Callable接口的call方法
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

 
分析:

FutureTask实现了Runnable接口的run()方法,在重写的run方法内部会调用你传入的Callable接口的实现类的call方法,如果你需要以多线程的方式执行的话,要么将FutureTask对象作为Thread参数(实现了Runnable接口)来创建Thread再调用start方法,要么使用线程池,尽管最后依旧是一样的。

整个大概的调用链:

thread的start()方法 -> -----> JNI的satrt0方法 -----> futureTask的run方法 —> callable的call方法

 
 
参考:
汪文君:Java高并发编程详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值