并发编程之线程的创建和启动
一、线程创建
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();
}
我们可以看到Runnable被FunctionInterface接口,说明使用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. 总结
- 实现
Runnable接口更好 -
Runnable方便配合线程池使用 -
Thread线程执行和线程创建无法解耦 -
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();
从上面代码的注释我们可以看到:
-
start()方法被synchronized进行修饰,为同步方法,这样避免了多次调用问题; - 使用
threadStatus(此变量被volatile修饰)记录线程状态;多次调用会抛出异常; - 这方法会重写的
run方法被虚拟机调用,是子线程执行的run方法。
上面已经介绍了start和run方法,接着我们写一个例子看看两个的区别:
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是异步的。










