并发编程(一)之线程的创建和启动

并发编程之线程的创建和启动

一、线程创建

1.1. 实现Runnable接口

实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target:

public class CreateThread implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ": 通过Runnable创建线程......");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread createThread = new Thread(new CreateThread(), "子线程");
        createThread.start();
        System.out.println(Thread.currentThread().getName() + "主线程......");
    }

}

我们看一下Runnable接口:


@FunctionalInterface
public interface Runnable {

    public abstract void run();

}

我们可以看到RunnableFunctionInterface接口,说明使用lamdba的写法去实现线程。很简洁。

public class CreateThread {

    public static void main(String[] args) {
        
        System.out.println(Thread.currentThread().getName() + "主线程......");

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + ": 通过Runnable创建线程......");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子线程1").start();
    }

}
1.2. 继承Thread

继承Thread类,重写run方法。其实Thread也是实现了Runnable接口,里面有很多native方法。后面会分析。

public class CreateThread1 extends Thread {

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ": 通过Runnable创建线程......");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread createThread = new CreateThread1();
        createThread.setName("子线程");
        createThread.start();
        System.out.println(Thread.currentThread().getName() + "主线程......");
    }

}

我们简单看见一下Thread里面的run方法:

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

这个里面的target其实就是我们传入的Runnable,这也是为啥我们可以实现Runnable接口的run方法,这也是就是继承Thread(把run重写)和实现Runnable(调用target.run()方法)的区别。

更值得我们注意的是run方法是异常的处理和抛出的,这意味的子线程发生异常,主线程是无法捕获到的(但是具体还是有处理的方法的,日后介绍,挖个坑,日后填)。

1.3. 总结
  1. 实现Runnable接口更好
  2. Runnable方便配合线程池使用
  3. Thread线程执行和线程创建无法解耦
  4. Thread继承之后无法继承其他线程,限制扩展性

最后再说一下 :创建线程我们可以有两种方法实现Runnable和继承Thread,但是本质来说创建线程只有一种(构造Thread类),run方法有两种传入Runnable通过target.run()调用和重写run()方法。

二、线程的启动

我们从上面的看到,线程的启动涉及到start()run()两种方法。

我们先看看start()方法:

/**
 * 1. start方法将导致当前线程开始执行。由JVM调用当前线程的run方法
 * 2. 结果是两个线程同时运行:当前线程(从对start方法的调用返回)和另一个线程(执行其run方法)
 * 3. 不允许多次启动线程, 线程一旦完成执行就不会重新启动 
 */
public synchronized void start() {
        /**
         * 对于VM创建/设置的主方法线程或“系统”组线程,不调用此方法。
         * 将来添加到此方法的任何新功能可能也必须添加到VM中.
         *
         * threadStatus = 0 意味着线程处于初始状态
         */
        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抛出了一个Throwable,那么它将被传递到调用堆栈上 */
            }
        }
    }

    private native void start0();

从上面代码的注释我们可以看到:

  1. start()方法被synchronized进行修饰,为同步方法,这样避免了多次调用问题;
  2. 使用threadStatus(此变量被volatile修饰)记录线程状态;多次调用会抛出异常;
  3. 这方法会重写的run方法被虚拟机调用,是子线程执行的run方法。

上面已经介绍了startrun方法,接着我们写一个例子看看两个的区别:

public static void main(String[] args) {

    Thread one = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "启动");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "线程1");

    Thread two = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "启动");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "线程2");
    one.start();
    two.run();
    System.out.println("主线程启动");

}

执行结果也很明显,调用run会阻塞主线程,start是异步的。

你可能感兴趣的:(并发编程)