[Java 并发编程] 6. 创建和开始Java线程

2020-08-12

一个Java线程就像一个可以执行你的Java代码的虚拟CPU。

当Java虚拟机创建的主线程开始执行main方法时,你的Java应用程序启动,在你的Java应用程序里面你可以创建和开始你自定义的线程。

Java 线程都是对象,就像其他的Java对象一样,线程对象都是 java.lang.Tread 的实例或者其子类的实例。

1 创建和开始线程

你可以像这样创建一个线程对象

1
Thread myThread = new Thread();

你可以像这样开始一个线程

1
myThread.start();

创建线程的几种方式:

  • 继承 java.lang.Thread 类并重写 run() 方法。
  • 实现 java.lang.Runnable 接口并重写 run() 方法。
  • 实现 java.util.concurrent.Callable 接口并重写 call() 方法。(备注:这种实现方式后面有 JUC 专题)

2 Thread 子类

继承 java.lang.Thread 类并重写 run() 方法

1
2
3
4
5
6
7
public class MyThread extends Thread {

public void run(){
System.out.println("MyThread running");
}

}

main方法

1
2
3
4
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.start();
}

start() 方法不会等线程执行完 run()方法,run() 方法将会由其他不同的CPU执行。


3 实现 Runnable 接口

第二种创建线程的方式是实现 java.lang.Runnable 接口并重写该接口的 run() 方法。一个 Java 对象实现 Runnable 接口 可以被Java Thread 类执行。

Runnable 接口由 JDK 提供,仅有一个 run 方法,是一个函数式接口。源码如下:

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}

实现 Runnable 的方式有3种:

  1. 创建一个 Java 类实现 Runnable 接口
  2. 创建一个 Java 匿名类实现 Runnable 接口
  3. 使用 Java 8 Lambda 表达式实现 Runnable 接口
3.1 创建一个 Java 类实现 Runnable 接口

例如:

1
2
3
4
5
6
7
public class MyRunnable implements Runnable {

public void run(){
System.out.println("MyRunnable running");
}

}
3.2 创建一个 Java 匿名类实现 Runnable 接口

例如:

1
2
3
4
5
6
7
Runnable myRunnable = new Runnable(){

public void run(){
System.out.println("Runnable running");
}

}
3.3 使用 Java 8 Lambda 表达式实现 Runnable 接口
1
Runnable runnable = () -> { System.out.println("Lambda Runnable running"); };
3.4 开启一个实现了 Runnable 接口的线程

java.lang.Thread的包含传入一个Runnable接口的构造函数。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//or an anonymous class, or lambda...
Runnable runnable = new MyRunnable();

//executed by a thread
Thread thread = new Thread(runnable);
thread.start();
}

4. 使用哪种方式创建线程更好 ?

关于继承Thread和实现Runnable接口,没有明确的规定用哪种方式创建线程更好,两种方式都可以让线程运行。我更倾向于使用实现Runnable接口的方式,后面我还们会了解到另外一种创建线程的方式,通过线程池创建线程的方式更加合理。

5. 常见陷阱:调用 run() 代替 start()

通过调用start(),程序会告诉 CPU 线程已准备就绪,等待CPU执行 run() 方法。若直接调用 run() 方法则不会开启一个新的线程去执行 run() 方法,而是在原来的线程中去执行 run() 方法,所以当我们需要一个新的线程去执行 run() 方法中的代码时,应该调用 start() 方法启动一个线程,而不是直接调用 run() 方法。

6. 线程名(Thread Names)

创建线程的时候可以指定线程名称,详见 java.lang.Thread API

1
2
3
4
5
6
7
public static void main(String[] args) {
Runnable myRunnable = () -> System.out.println("Thread name is " + Thread.currantThread().getName());

new Thread(myRunnable, "Thread One").start();
}

console out: Thread name is Thread One

7. Thread.currentThread()

通过 Thread.currentThread() 获取当前正在执行的线程对象。

1
Thread currentThread = Thread.currentThread();

8. 暂停线程

可以通过 Thread.sheep() 让线程睡眠指定毫秒数。这里不建议使用 thread.suspend() 方法,该方法和 Thread 类的 resume()、stop()、destroy() 都是 JDK 标注废弃方法。

1
2
3
4
5
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}

注意: Thread.sheep() 方法不会释放CPU资源,如果设置锁的话也不会释放锁资源,只是让线程睡眠指定毫秒数。与 Object 类的 wait() 方法不同, wait() 方法主要用于线程通信,且 wait() 方法会释放锁资源。

9. 停止一个线程

不建议使用 Thread 类提供的 stop() 方法去停止一个线程,stop() 方法会暴力停止一个线程,对程序不友好,可以通过更友好的方式停止一个线程。

请看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MyRunnable implements Runnable {

private boolean doStop = false;

public synchronized void doStop() {
this.doStop = true;
}

private synchronized boolean keepRunning() {
return this.doStop == false;
}

@Override
public void run() {
while(keepRunning()) {
// keep doing what this thread should do.
System.out.println("Running");

try {
Thread.sleep(3L * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}
}

请注意 doStop() 方法和 keepRunning() 方法使用了 synchronized 关键字,后面会详细讲解 synchronized 关键字的作用,这里不做过多描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyRunnableMain {

public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();

Thread thread = new Thread(myRunnable);

thread.start();

try {
Thread.sleep(10L * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}

myRunnable.doStop();
}
}

上面示例通过设置一个标识,主线程里面启动子线程,然后主线程睡眠10秒钟后调用 doStop() 方法,实现在主线程中停止子线程的运行。