0
点赞
收藏
分享

微信扫一扫

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


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

一、线程创建

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. 总结
  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​​方法。

上面已经介绍了​​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​​是异步的。


举报

相关推荐

0 条评论