Java 线程的创建和启动
Java 使用 Thread 类代表线程,所有的线程对象都必须是 Thread 类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码)。 Java 使用线程执行体来代表这段程序流。
继承 Thread 类创建线程类
通过继承 Thread 类来创建并启动多线程的步骤如下。
- 定义 Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程需要完成的任务。因此把 run() 方法称为线程执行体。
- 创建 Thread 子类的实例,即创建了线程对象。
- 调用线程对象的 start() 方法来启动该线程。
下面程序示范了通过继承 Thread 类来创建并启动多线程。
- //通过继承Thread类来创建线程类
- public class FirstThread extends Thread {
- private int i = 0;
- // 重写run()方法的方法体就是线程执行体
- public void run() {
- for (; i < 100; i++) {
- // 当线程类继承Thread时,直接使用this即可获取当前线程
- // Thread对象的getName()方法返回当前线程的名字
- // 因此可以直接调用getName()方法返回当前线程的名字
- System.out.println(getName() + " " + i);
- }
- }
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- // 调用Thread的currentThread()方法获取当前线程
- System.out.println(Thread.currentThread().getName() + " " + i);
- if (i == 20) {
- // 创建并启动第一个线程
- new FirstThread().start();
- // 创建并启动第二个线程
- new FirstThread().start();
- }
- }
- }
- }
- main 0
- main 1
- main 2
- main 3
- main 4
- main 5
- main 6
- main 7
- main 8
- main 9
- main 10
- main 11
- main 12
- main 13
- main 14
- main 15
- main 16
- main 17
- main 18
- main 19
- main 20
- main 21
- main 22
- main 23
- main 24
- main 25
- main 26
- main 27
- main 28
- main 29
- main 30
- main 31
- Thread-1 0
- Thread-1 1
- main 32
- Thread-1 2
- Thread-1 3
- main 33
- Thread-1 4
- Thread-1 5
- Thread-1 6
- Thread-1 7
- main 34
- main 35
- main 36
- main 37
- main 38
- Thread-0 0
- Thread-0 1
- Thread-0 2
- Thread-0 3
- Thread-0 4
- Thread-1 8
- Thread-0 5
- main 39
- Thread-0 6
- Thread-0 7
- Thread-0 8
- Thread-0 9
- Thread-1 9
- Thread-0 10
- Thread-0 11
- Thread-0 12
- Thread-0 13
- main 40
- Thread-0 14
- Thread-0 15
- Thread-0 16
- Thread-0 17
- Thread-0 18
- Thread-0 19
- Thread-0 20
- Thread-1 10
- Thread-1 11
- Thread-1 12
- Thread-1 13
- Thread-1 14
- Thread-1 15
- Thread-1 16
- Thread-1 17
- Thread-1 18
- Thread-1 19
- Thread-1 20
- Thread-1 21
- Thread-1 22
- Thread-1 23
- Thread-1 24
- Thread-1 25
- Thread-1 26
- Thread-1 27
- Thread-1 28
- Thread-1 29
- Thread-1 30
- Thread-1 31
- Thread-1 32
- Thread-1 33
- Thread-1 34
- Thread-1 35
- Thread-1 36
- Thread-1 37
- Thread-1 38
- Thread-1 39
- Thread-1 40
- Thread-1 41
- Thread-1 42
- Thread-1 43
- Thread-1 44
- Thread-1 45
- Thread-1 46
- Thread-1 47
- Thread-1 48
- Thread-1 49
- Thread-1 50
- Thread-1 51
- Thread-1 52
- Thread-1 53
- Thread-1 54
- Thread-1 55
- Thread-1 56
- Thread-1 57
- Thread-1 58
- Thread-1 59
- Thread-1 60
- Thread-1 61
- Thread-0 21
- main 41
- Thread-0 22
- Thread-0 23
- Thread-1 62
- Thread-0 24
- Thread-0 25
- Thread-0 26
- Thread-0 27
- Thread-0 28
- Thread-0 29
- Thread-0 30
- Thread-0 31
- Thread-0 32
- Thread-0 33
- Thread-0 34
- Thread-0 35
- Thread-0 36
- Thread-0 37
- Thread-0 38
- Thread-0 39
- Thread-0 40
- main 42
- Thread-0 41
- Thread-0 42
- Thread-0 43
- Thread-0 44
- Thread-0 45
- Thread-0 46
- Thread-0 47
- Thread-0 48
- Thread-0 49
- Thread-0 50
- Thread-1 63
- Thread-1 64
- Thread-1 65
- Thread-1 66
- Thread-1 67
- Thread-1 68
- Thread-1 69
- Thread-1 70
- Thread-1 71
- Thread-1 72
- Thread-1 73
- Thread-1 74
- Thread-1 75
- Thread-1 76
- Thread-1 77
- Thread-1 78
- Thread-1 79
- Thread-1 80
- Thread-1 81
- Thread-1 82
- Thread-1 83
- Thread-1 84
- Thread-1 85
- Thread-1 86
- Thread-1 87
- Thread-1 88
- Thread-1 89
- Thread-1 90
- Thread-1 91
- Thread-1 92
- Thread-1 93
- Thread-1 94
- Thread-1 95
- Thread-1 96
- Thread-1 97
- Thread-1 98
- Thread-1 99
- Thread-0 51
- Thread-0 52
- Thread-0 53
- Thread-0 54
- Thread-0 55
- Thread-0 56
- Thread-0 57
- Thread-0 58
- Thread-0 59
- Thread-0 60
- Thread-0 61
- Thread-0 62
- Thread-0 63
- Thread-0 64
- Thread-0 65
- Thread-0 66
- Thread-0 67
- Thread-0 68
- Thread-0 69
- Thread-0 70
- Thread-0 71
- Thread-0 72
- Thread-0 73
- Thread-0 74
- Thread-0 75
- Thread-0 76
- Thread-0 77
- Thread-0 78
- Thread-0 79
- Thread-0 80
- Thread-0 81
- Thread-0 82
- Thread-0 83
- Thread-0 84
- Thread-0 85
- Thread-0 86
- Thread-0 87
- Thread-0 88
- Thread-0 89
- Thread-0 90
- Thread-0 91
- Thread-0 92
- Thread-0 93
- Thread-0 94
- Thread-0 95
- Thread-0 96
- Thread-0 97
- Thread-0 98
- Thread-0 99
- main 43
- main 44
- main 45
- main 46
- main 47
- main 48
- main 49
- main 50
- main 51
- main 52
- main 53
- main 54
- main 55
- main 56
- main 57
- main 58
- main 59
- main 60
- main 61
- main 62
- main 63
- main 64
- main 65
- main 66
- main 67
- main 68
- main 69
- main 70
- main 71
- main 72
- main 73
- main 74
- main 75
- main 76
- main 77
- main 78
- main 79
- main 80
- main 81
- main 82
- main 83
- main 84
- main 85
- main 86
- main 87
- main 88
- main 89
- main 90
- main 91
- main 92
- main 93
- main 94
- main 95
- main 96
- main 97
- main 98
- main 99
运行结果
上面程序中的 FirstThread 类继承了 Thread 类,并实现了 run() 方法,该 run() 方法里的代码执行流就是该线程所需要完成的任务。程序的主方法中也包含了一个循环,当循环变量 i 等于20时创建并启动两个新线程。
虽然上面程序只显式地创建并启动了2个线程,但实际上程序有3个线程,即程序显式创建的2个子线程和主线程。前面已经提到,当 Java 程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体不是由 run() 方法确定的,而是由 main() 方法确定的——main() 方法的方法体代表主线程的线程执行体。
注意:进行多线程编程时不要忘记了 Java 程序运行时默认的主线程,main() 方法的方法体就是主线程的线程执行体。
除此之外,上面程序还用到了线程的如下两个方法。
- Thread.currentThread(): currentThread() 是 Thread 类的静态方法,该方法总是返回当前正在执行的线程对象。
- getName():该方法是 Thread 类的实例方法,该方法返回调用该方法的线程名字。
提示:程序可以通过 setName(String name) 方法为线程设置名字,也可以通过 getName() 方法返回指定线程的名字。在默认情况下,主线程的名字为 main ,用户启动的多个线程的名字依次为 Thread-0、 Thread-1、 Thread-2、…、 Thread-n 等。
从运行结果可以看出 Thread-0 和 Thread-1 两个线程输出的 i 变量不连续——注意:i 变量是 FirstThread 的实例变量,而不是局部变量,但因为程序每次创建线程对象时都需要创建一个 FirstThread 对象,所以 Thread-0 和 Thread-1 不能共享该实例变量。
注意:使用继承 Thread 类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
实现 Runnable 接口来创建线程类
步骤如下:
- 定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
- 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
代码如下所示:
- //创建 Runnable 实现类的对象
- SecondThread st = new SecondThread();
- //以 Runnable 实现类的对象作为 Thread 的 target 来创建 Thread 对象,即线程对象
- new Thread(st);
也可以在创建 Thread 对象时为该 Thread 对象指定一个名字,代码如下所示:
- //创建 Thread 对象时指定 target 和新线程的名字
- new Thread (st , "新线程1");
提示:Runnable 对象仅仅作为 Thread 对象的 target,Runnable 实现类里包含的 run() 方法仅作为线程执行体。而实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执行其 target 的 run() 方法。
- public class SecondThread implements Runnable {
- private int i;
- // run()方法同样是线程执行体
- public void run() {
- for (; i < 100; i++) {
- // 当线程类实现Runnable接口时
- // 如果想获取当前线程,只能用Thread.currentThread()方法
- System.out.println(Thread.currentThread().getName() + " " + i);
- }
- }
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + " " + i);
- if (i == 20) {
- SecondThread st = new SecondThread(); // ①
- // 通过new Thread(target, name)方法创建新线程
- new Thread(st, "线程1").start();
- new Thread(st, "线程2").start();
- }
- }
- }
- }
上面程序中的粗体字代码部分实现了 run() 方法,也就是定义了该线程的线程执行体。对比 FirstThread 中的 run() 方法体和 SecondThread 中的 run() 方法体不难发现,通过继承 Thread 类来获得当前线程对象比较简单,直接使用 this 就可以了;但通过实现 Runnable 接口来获得当前线程对象,则必须使用 Thread.currentThread() 方法。
提示:Runnable 接口中只包含一个抽象方法 ,从 Java 8 开 始, Runnable 接口使用了@FunctionalInterface 修饰。也就是说,Runnable 接口是函数式接口,可使用 Lambda 表达式创建 Runnable 对象。接下来介绍的 Callable 接口也是函数式接口。
除此之外,上面程序中的粗体字代码创建了两个 Thread 对象,并调用 start() 方法来启动这两个线程。在 FirstThread 和 SecondThread 中创建线程对象的方式有所区别:前者直接创建的 Thread 子类即可代表线程对象;后者创建的 Runnable 对象只能作为线程对象的 target 。
运行上面程序,会看到如下结果:
- main 0
- main 1
- main 2
- main 3
- main 4
- main 5
- main 6
- main 7
- main 8
- main 9
- main 10
- main 11
- main 12
- main 13
- main 14
- main 15
- main 16
- main 17
- main 18
- main 19
- main 20
- main 21
- main 22
- main 23
- main 24
- main 25
- main 26
- 线程2 0
- 线程2 1
- main 27
- main 28
- 线程2 2
- main 29
- main 30
- main 31
- 线程2 3
- 线程1 3
- main 32
- 线程1 5
- 线程2 4
- 线程1 6
- main 33
- 线程1 8
- 线程2 7
- 线程1 9
- main 34
- 线程1 11
- 线程2 10
- 线程1 12
- main 35
- 线程1 14
- 线程2 13
- 线程1 15
- 线程1 17
- 线程1 18
- 线程1 19
- 线程1 20
- 线程1 21
- 线程1 22
- 线程1 23
- 线程1 24
- 线程1 25
- 线程1 26
- 线程1 27
- main 36
- 线程1 28
- 线程2 16
- 线程1 29
- main 37
- 线程1 31
- 线程2 30
- 线程1 32
- main 38
- 线程1 34
- 线程2 33
- 线程1 35
- main 39
- 线程1 37
- 线程2 36
- 线程1 38
- 线程1 40
- 线程1 41
- 线程1 42
- 线程1 43
- 线程1 44
- 线程1 45
- main 40
- main 41
- main 42
- main 43
- main 44
- main 45
- main 46
- main 47
- main 48
- main 49
- main 50
- main 51
- main 52
- main 53
- main 54
- main 55
- main 56
- 线程1 46
- 线程1 47
- 线程1 48
- 线程1 49
- 线程1 50
- 线程1 51
- 线程1 52
- 线程1 53
- 线程1 54
- 线程1 55
- 线程1 56
- 线程1 57
- 线程1 58
- 线程1 59
- 线程1 60
- 线程1 61
- 线程1 62
- 线程1 63
- 线程1 64
- 线程1 65
- 线程1 66
- 线程1 67
- 线程1 68
- 线程1 69
- 线程1 70
- 线程1 71
- 线程1 72
- 线程1 73
- 线程1 74
- 线程1 75
- 线程1 76
- 线程1 77
- 线程1 78
- 线程1 79
- 线程1 80
- 线程1 81
- 线程1 82
- 线程1 83
- 线程1 84
- 线程1 85
- 线程1 86
- 线程1 87
- 线程1 88
- 线程1 89
- 线程1 90
- 线程1 91
- 线程1 92
- 线程1 93
- 线程1 94
- 线程1 95
- 线程1 96
- 线程1 97
- 线程1 98
- 线程1 99
- 线程2 39
- main 57
- main 58
- main 59
- main 60
- main 61
- main 62
- main 63
- main 64
- main 65
- main 66
- main 67
- main 68
- main 69
- main 70
- main 71
- main 72
- main 73
- main 74
- main 75
- main 76
- main 77
- main 78
- main 79
- main 80
- main 81
- main 82
- main 83
- main 84
- main 85
- main 86
- main 87
- main 88
- main 89
- main 90
- main 91
- main 92
- main 93
- main 94
- main 95
- main 96
- main 97
- main 98
- main 99
运行结果
两个子线程的 i 变量是连续的,也就是釆用 Runnable 接口的方式创建的多个线程可以共享线程类的实例变量。这是因为在这种方式下,程序所创建的 Runnable 对象只是线程的 target ,而多个线程可以共享同一个 target ,所以多个线程可以共享同一个线程类(实际上应该是线程的 target 类)的实例变量。
使用 Callable 和 Future 创建线程
前面已经指出,通过实现 Runnable 接口创建多线程时, Thread 类的作用就是把 run() 方法包装成线程执行体。那么是否可以直接把任意方法都包装成线程执行体呢? Java 目前不行!但 Java 的模仿者 C# 可以(C# 可以把任意方法包装成线程执行体,包括有返回值的方法)。
也许受此启发,从 Java 5 开始, Java 提供了 Callable 接口,该接口怎么看都像是 Runnable 接口的增强版, Callable 接口提供了一个 call() 方法可以作为线程执行体,但 call() 方法比 run() 方法功能更强大。
- call() 方法可以有返回值。
- call() 方法可以声明抛出异常。
因此完全可以提供一个 Callable 对象作为 Thread 的 target ,而该线程的线程执行体就是该 Callable 对象的 call() 方法。问题是: Callable 接口是 Java 5 新增的接口,而且它不是 Runnable 接口的子接口,所以 Callable 对象不能直接作为 Thread 的 target 。而且 call() 方法还有一个返回值——call() 方法并不是直接调用,它是作为线程执行体被调用的。那么如何获取 call() 方法的返回值呢?
Java 5 提供了 Future 接口来代表 Callable 接口里 call() 方法的返回值,并为 Future 接口提供了一个FutureTask 实现类,该实现类实现了 Future 接口,并实现了 Runnable 接口 ——可以作为 Thread 类的 target 。
在 Future 接口里定义了如下几个公共方法来控制它关联的 Callable 任务。
- boolean cancel(boolean maylnterruptlfRunning):试图取消该 Future 里关联的 Callable 任务。
- V get():返回 Callable 任务里 call() 方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
- V get(long timeout, TimeUnit unit):返回 Callable 任务里 call() 方法的返回值。该方法让程序最多阻塞 timeout 和 unit 指定的时间,如果经过指定时间后 Callable 任务依然没有返回值,将会抛出 TimeoutException 异常。
- boolean isCancelled():如果在 Callable 任务正常完成前被取消,则返回 true 。
- boolean isDone ():如 果 Callable 任务已完成,则返回 true 。
注意:Callable 接口有泛型限制, Callable 接口里的泛型形参类型与 call() 方法返回值类型相同。而且 Callable 接口是函数式接口,因此可使用 Lambda 表达式创建 Callable 对象。
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,且该 call() 方法有返回值,再创建 Callable 实现类的实例。从 Java 8 开始,可以直接使用 Lambda 表达式创建 Callable对象。
- 使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
下面程序通过实现 Callable 接口来实现线程类,并启动该线程。
- import java.util.concurrent.Callable;
- import java.util.concurrent.FutureTask;
- public class ThirdThread {
- public static void main(String[] args) {
- // 创建Callable对象
- ThirdThread rt = new ThirdThread();
- // 先使用Lambda表达式创建Callable<Integer>对象
- // 使用FutureTask来包装Callable对象
- FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>) () -> {
- int i = 0;
- for (; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + " 的循环变量 i 的值: " + i);
- }
- return i;
- });
- for (int i = 0; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + " 的循环变量 i 的值: " + i);
- if (i == 20) {
- // 实质还是以Callable对象来创建并启动线程的
- new Thread(task, "有返回值的线程").start();
- }
- }
- try {
- System.out.println("子线程的返回值:" + task.get());
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
上面程序中使用 Lambda 表达式直接创建了 Callable 对象,这样就无须先创建 Callable 实现类,再创建 Callable 对象了。实现 Callable 接口与实现 Runnable 接口并没有太大的差别,只是 Callable 的 call() 方法允许声明抛出异常,而且允许带返回值。
上面程序中的粗体字代码是以 Callable 对象来启动线程的关键代码。程序先使用 Lambda 表达式创建一个 Callable 对象,然后将该实例包装成一个 FutureTask 对象。主线程中当循环变量 i 等于20时,程序启动以 FutureTask 对象为 target 的线程。程序最后调用 FutureTask 对象的 get() 方法来返回 call() 方法的返回值——该方法将导致主线程被阻塞,直到 call() 方法结束并返回为止。
运行上面程序,将看到主线程和 call() 方法所代表的线程交替执行的情形,程序最后还会输出 call() 方法的返回值。
- main 的循环变量 i 的值: 0
- main 的循环变量 i 的值: 1
- main 的循环变量 i 的值: 2
- main 的循环变量 i 的值: 3
- main 的循环变量 i 的值: 4
- main 的循环变量 i 的值: 5
- main 的循环变量 i 的值: 6
- main 的循环变量 i 的值: 7
- main 的循环变量 i 的值: 8
- main 的循环变量 i 的值: 9
- main 的循环变量 i 的值: 10
- main 的循环变量 i 的值: 11
- main 的循环变量 i 的值: 12
- main 的循环变量 i 的值: 13
- main 的循环变量 i 的值: 14
- main 的循环变量 i 的值: 15
- main 的循环变量 i 的值: 16
- main 的循环变量 i 的值: 17
- main 的循环变量 i 的值: 18
- main 的循环变量 i 的值: 19
- main 的循环变量 i 的值: 20
- main 的循环变量 i 的值: 21
- main 的循环变量 i 的值: 22
- main 的循环变量 i 的值: 23
- main 的循环变量 i 的值: 24
- main 的循环变量 i 的值: 25
- main 的循环变量 i 的值: 26
- main 的循环变量 i 的值: 27
- main 的循环变量 i 的值: 28
- main 的循环变量 i 的值: 29
- main 的循环变量 i 的值: 30
- main 的循环变量 i 的值: 31
- main 的循环变量 i 的值: 32
- main 的循环变量 i 的值: 33
- main 的循环变量 i 的值: 34
- main 的循环变量 i 的值: 35
- main 的循环变量 i 的值: 36
- main 的循环变量 i 的值: 37
- main 的循环变量 i 的值: 38
- main 的循环变量 i 的值: 39
- main 的循环变量 i 的值: 40
- main 的循环变量 i 的值: 41
- main 的循环变量 i 的值: 42
- main 的循环变量 i 的值: 43
- main 的循环变量 i 的值: 44
- main 的循环变量 i 的值: 45
- main 的循环变量 i 的值: 46
- main 的循环变量 i 的值: 47
- main 的循环变量 i 的值: 48
- main 的循环变量 i 的值: 49
- main 的循环变量 i 的值: 50
- main 的循环变量 i 的值: 51
- main 的循环变量 i 的值: 52
- main 的循环变量 i 的值: 53
- main 的循环变量 i 的值: 54
- main 的循环变量 i 的值: 55
- main 的循环变量 i 的值: 56
- main 的循环变量 i 的值: 57
- main 的循环变量 i 的值: 58
- main 的循环变量 i 的值: 59
- main 的循环变量 i 的值: 60
- main 的循环变量 i 的值: 61
- main 的循环变量 i 的值: 62
- main 的循环变量 i 的值: 63
- main 的循环变量 i 的值: 64
- main 的循环变量 i 的值: 65
- main 的循环变量 i 的值: 66
- main 的循环变量 i 的值: 67
- main 的循环变量 i 的值: 68
- main 的循环变量 i 的值: 69
- main 的循环变量 i 的值: 70
- main 的循环变量 i 的值: 71
- main 的循环变量 i 的值: 72
- main 的循环变量 i 的值: 73
- 有返回值的线程 的循环变量 i 的值: 0
- main 的循环变量 i 的值: 74
- 有返回值的线程 的循环变量 i 的值: 1
- main 的循环变量 i 的值: 75
- 有返回值的线程 的循环变量 i 的值: 2
- 有返回值的线程 的循环变量 i 的值: 3
- 有返回值的线程 的循环变量 i 的值: 4
- 有返回值的线程 的循环变量 i 的值: 5
- 有返回值的线程 的循环变量 i 的值: 6
- 有返回值的线程 的循环变量 i 的值: 7
- 有返回值的线程 的循环变量 i 的值: 8
- 有返回值的线程 的循环变量 i 的值: 9
- 有返回值的线程 的循环变量 i 的值: 10
- 有返回值的线程 的循环变量 i 的值: 11
- 有返回值的线程 的循环变量 i 的值: 12
- 有返回值的线程 的循环变量 i 的值: 13
- 有返回值的线程 的循环变量 i 的值: 14
- 有返回值的线程 的循环变量 i 的值: 15
- 有返回值的线程 的循环变量 i 的值: 16
- 有返回值的线程 的循环变量 i 的值: 17
- 有返回值的线程 的循环变量 i 的值: 18
- 有返回值的线程 的循环变量 i 的值: 19
- 有返回值的线程 的循环变量 i 的值: 20
- 有返回值的线程 的循环变量 i 的值: 21
- 有返回值的线程 的循环变量 i 的值: 22
- 有返回值的线程 的循环变量 i 的值: 23
- 有返回值的线程 的循环变量 i 的值: 24
- 有返回值的线程 的循环变量 i 的值: 25
- 有返回值的线程 的循环变量 i 的值: 26
- 有返回值的线程 的循环变量 i 的值: 27
- 有返回值的线程 的循环变量 i 的值: 28
- 有返回值的线程 的循环变量 i 的值: 29
- 有返回值的线程 的循环变量 i 的值: 30
- 有返回值的线程 的循环变量 i 的值: 31
- 有返回值的线程 的循环变量 i 的值: 32
- 有返回值的线程 的循环变量 i 的值: 33
- 有返回值的线程 的循环变量 i 的值: 34
- 有返回值的线程 的循环变量 i 的值: 35
- 有返回值的线程 的循环变量 i 的值: 36
- 有返回值的线程 的循环变量 i 的值: 37
- 有返回值的线程 的循环变量 i 的值: 38
- 有返回值的线程 的循环变量 i 的值: 39
- 有返回值的线程 的循环变量 i 的值: 40
- 有返回值的线程 的循环变量 i 的值: 41
- 有返回值的线程 的循环变量 i 的值: 42
- 有返回值的线程 的循环变量 i 的值: 43
- 有返回值的线程 的循环变量 i 的值: 44
- 有返回值的线程 的循环变量 i 的值: 45
- 有返回值的线程 的循环变量 i 的值: 46
- 有返回值的线程 的循环变量 i 的值: 47
- 有返回值的线程 的循环变量 i 的值: 48
- 有返回值的线程 的循环变量 i 的值: 49
- 有返回值的线程 的循环变量 i 的值: 50
- 有返回值的线程 的循环变量 i 的值: 51
- 有返回值的线程 的循环变量 i 的值: 52
- 有返回值的线程 的循环变量 i 的值: 53
- 有返回值的线程 的循环变量 i 的值: 54
- 有返回值的线程 的循环变量 i 的值: 55
- 有返回值的线程 的循环变量 i 的值: 56
- 有返回值的线程 的循环变量 i 的值: 57
- 有返回值的线程 的循环变量 i 的值: 58
- 有返回值的线程 的循环变量 i 的值: 59
- 有返回值的线程 的循环变量 i 的值: 60
- 有返回值的线程 的循环变量 i 的值: 61
- 有返回值的线程 的循环变量 i 的值: 62
- 有返回值的线程 的循环变量 i 的值: 63
- 有返回值的线程 的循环变量 i 的值: 64
- 有返回值的线程 的循环变量 i 的值: 65
- 有返回值的线程 的循环变量 i 的值: 66
- 有返回值的线程 的循环变量 i 的值: 67
- 有返回值的线程 的循环变量 i 的值: 68
- 有返回值的线程 的循环变量 i 的值: 69
- 有返回值的线程 的循环变量 i 的值: 70
- 有返回值的线程 的循环变量 i 的值: 71
- 有返回值的线程 的循环变量 i 的值: 72
- 有返回值的线程 的循环变量 i 的值: 73
- 有返回值的线程 的循环变量 i 的值: 74
- 有返回值的线程 的循环变量 i 的值: 75
- 有返回值的线程 的循环变量 i 的值: 76
- 有返回值的线程 的循环变量 i 的值: 77
- 有返回值的线程 的循环变量 i 的值: 78
- 有返回值的线程 的循环变量 i 的值: 79
- 有返回值的线程 的循环变量 i 的值: 80
- 有返回值的线程 的循环变量 i 的值: 81
- 有返回值的线程 的循环变量 i 的值: 82
- 有返回值的线程 的循环变量 i 的值: 83
- 有返回值的线程 的循环变量 i 的值: 84
- 有返回值的线程 的循环变量 i 的值: 85
- 有返回值的线程 的循环变量 i 的值: 86
- 有返回值的线程 的循环变量 i 的值: 87
- 有返回值的线程 的循环变量 i 的值: 88
- 有返回值的线程 的循环变量 i 的值: 89
- 有返回值的线程 的循环变量 i 的值: 90
- 有返回值的线程 的循环变量 i 的值: 91
- 有返回值的线程 的循环变量 i 的值: 92
- 有返回值的线程 的循环变量 i 的值: 93
- 有返回值的线程 的循环变量 i 的值: 94
- 有返回值的线程 的循环变量 i 的值: 95
- 有返回值的线程 的循环变量 i 的值: 96
- 有返回值的线程 的循环变量 i 的值: 97
- 有返回值的线程 的循环变量 i 的值: 98
- 有返回值的线程 的循环变量 i 的值: 99
- main 的循环变量 i 的值: 76
- main 的循环变量 i 的值: 77
- main 的循环变量 i 的值: 78
- main 的循环变量 i 的值: 79
- main 的循环变量 i 的值: 80
- main 的循环变量 i 的值: 81
- main 的循环变量 i 的值: 82
- main 的循环变量 i 的值: 83
- main 的循环变量 i 的值: 84
- main 的循环变量 i 的值: 85
- main 的循环变量 i 的值: 86
- main 的循环变量 i 的值: 87
- main 的循环变量 i 的值: 88
- main 的循环变量 i 的值: 89
- main 的循环变量 i 的值: 90
- main 的循环变量 i 的值: 91
- main 的循环变量 i 的值: 92
- main 的循环变量 i 的值: 93
- main 的循环变量 i 的值: 94
- main 的循环变量 i 的值: 95
- main 的循环变量 i 的值: 96
- main 的循环变量 i 的值: 97
- main 的循环变量 i 的值: 98
- main 的循环变量 i 的值: 99
- 子线程的返回值:100
运行结果
创建线程的三种方式对比
通过继承 Thread 类或实现 Runnable、Callable 接口都可以实现多线程,不过实现 Runnable 接口与实现 Callable 接口的方式基本相同,只是 Callable 接口里定义的方法有返回值,可以声明抛出异常而已。因此可以将实现 Runnable 接口和实现 Callable 接口归为一种方式。这种方式与继承 Thread 方式之间的主要差别如下。
采用实现 Runnable 、 Callable 接口的方式创建多线程的优缺点:
- 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU 、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
- 劣势是,编程稍稍复杂,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法。
采用继承 Thread 类的方式创建多线程的优缺点:
- 劣势是,因为线程类已经继承了 Thread 类,所以不能再继承其他父类。
- 优势是,编写简单,如果需要访问当前线程,则无须使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
鉴于上面分析,因此一般推荐采用实现 Runnable 接口、Callable 接口的方式来创建多线程。
Java 线程的创建和启动的更多相关文章
- Java线程:创建与启动
Java线程:创建与启动 一.定义线程 1.扩展java.lang.Thread类. 此类中有个run()方法,应该注意其用法: public void run() 如果该线程是使用独立的 R ...
- Java线程的创建及启动
1.继承Thread类,重写该类的run()方法. package samTest; import java.util.Scanner; /** * Created by Sam on 2018-01 ...
- 漫谈并发编程(二):java线程的创建与基本控制
java线程的创建 定义任务 在java中使用任务这个名词来表示一个线程控制流的代码段,用Runnable接口来标记一个任务,该接口的run方法为线程运行的代码段. public ...
- 03_线程的创建和启动_实现Runnable接口方式
[线程的创建和启动的步骤(实现Runnable接口方式)] 1.定义Runnable接口的实现类,并重写其中的run方法.run()方法的方法体是线程执行体. class SonThread imp ...
- JAVA - 线程从创建到死亡的几种状态都有哪些?
JAVA - 线程从创建到死亡的几种状态都有哪些? 新建( new ):新创建了一个线程对象. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 sta ...
- Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明
Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明 作者: Grey 原文地址: 博客园:Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明 C ...
- JAVA学习笔记16——线程的创建和启动
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例.每个线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码).Java使用线程执行体来代表这段 ...
- 04_线程的创建和启动_使用Callable和Future的方式
[简述] 从java5开始,java提供了Callable接口,这个接口可以是Runnable接口的增强版, Callable接口提供了一个call()方法作为线程执行体,call()方法比run() ...
- 02_线程的创建和启动_继承Thread方式
[简述] java使用Thread类代表线程,所有的线程都必须是Thread或者其子类的实例. 每个线程的任务就是完成一定的任务,实际上就是执行一段程序流. [创建并启动多线程的步骤(集成Thread ...
随机推荐
- xpath 解析 及案例
xpath解析 编码流程: 1.实例化一个etree对象,且将页面源码加载到该对象中 2.使用xpath函数,且在函数中必须作用一个xpath表达式进行标签的定位 3.使用xpath进行属性和文本的提 ...
- centos7下安装docker(15.3跨主机网络-macvlan)
除了ovrlay,docker还开发了另一个支持跨主机容器的driver:macvlan macvlan本身是linu kernel模块,其功能是允许在同一物理网卡上配置多了MAC地址,即:多个int ...
- centos7 mongodb安装
参考文档 http://www.runoob.com/mongodb/mongodb-connections.html https://www.cnblogs.com/layezi/p/7290082 ...
- 深入学习Redis:Redis内存模型
每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code 一.Redis内存统计 工欲善其事必先利其器,在说明Redis内存之前首先说明如何统计 ...
- 爬取伯乐在线文章(二)通过xpath提取源文件中需要的内容
爬取说明 以单个页面为例,如:http://blog.jobbole.com/110287/ 我们可以提取标题.日期.多少个评论.正文内容等 Xpath介绍 1. xpath简介 (1) xpath使 ...
- 测试 Flask 应用
测试 Flask 应用 没有经过测试的东西都是不完整的 这一箴言的起源已经不可考了,尽管他不是完全正确的,但是仍然离真理不远.没有测试过的应用将会使得提高现有代码质量很困难,二不测试应用程序的开发者, ...
- 论文笔记(一)---翻译 Rich feature hierarchies for accurate object detection and semantic segmentation
论文网址: https://arxiv.org/abs/1311.2524 RCNN利用深度学习进行目标检测. 摘要 可以将ImageNet上的进全图像分类而训练好的大型卷积神经网络用到PASCAL的 ...
- kafka+storm结合存在的一些问题与解决方法
在配置kafka和storm的时候, 经常的会出现一些问题, 主要在以下几个: 1. 打jar包上去storm集群的时候会出现jar包冲突,类似于log4j或者sf4j的报错信息. 2. kafka ...
- UIToolBar - 官方文档
继承关系:UIToolBar -> UIView -> UIResponder -> NSObject. toolBar是一个工具栏,用于显示一个或多个按钮.其按钮叫做toolBar ...
- 【转】强化学习(一)Deep Q-Network
原文地址:https://www.hhyz.me/2018/08/05/2018-08-05-RL/ 1. 前言 虽然将深度学习和增强学习结合的想法在几年前就有人尝试,但真正成功的开端就是DeepMi ...