【图灵干货】Java高级教程第七节:Java 多线程编程
2021-11-01 16:43:42
Java为多线程编程提供内置支持。一个线程是指一个单一顺序的控制流,一个过程可以并发多个线程,每个线程并行执行不同的任务。
多线程是多任务的一种特殊形式,但多线程使用的资源成本较小。
另一个与线程相关的术语——过程:一个过程包括由操作系统分配的内存空间,包括一个或多个线程。线程不能独立存在,它必须是过程的一部分。一个过程一直在运行,直到所有非守护线程结束。
多线程可以满足程序员编写高效程序来充分利用CPU的目的。
第二,线程的生命周期。
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个完整的线程生命周期。
Java高级教程第七节:Java多线程编程。
新建状态:
使用new关键字和Thread或其子类建立线程对象后,线程对象处于新状态。直到程序start()这个线程,它才保持这种状态。
就绪状态:
当线程对象调用start()方法时,线程进入就绪状态。就绪线程在就绪队列中,等待JVM里程调度器的调度。
运行状态:
若线程在就绪状态下获得CPU资源,则可执行run(),此时线程处于运行状态。运行状态下的线程最复杂,可以变成阻塞状态、就绪状态和死亡状态。
阻塞状态:
若线程执行sleep(睡眠)、suspend(挂起)等方法,则在失去所占用的资源后,线程将从运行状态进入阻塞状态。在睡眠时间到达或获得设备资源后,可以重新进入就绪状态。可分为三类:
等待阻塞:运行状态下的线程执行wait()方法,使线程进入等待阻塞状态。
同步阻塞:线程未能获得synchronized同步锁(因为同步锁被其他线程占用)。
其它阻塞:当调用线程的sleep()或join()发出I/O请求时,线程将进入阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或I/O处理完成后,线程再次转入就绪状态。
死亡状态:
当一个运行状态的线程完成任务或其他终止条件时,线程切换到终止状态。
三、线程优先级。
每个java线程都有一个优先级,这有助于操作系统确定线程的调度顺序。
Java线程的优先级是一个整数,其值范围为1(Thread.MIN_PRIORITY)-10(Thread.MAX_PRIORITY)。
默认情况下,每个线程将分配一个优先级NORM_PRIORITY(5)。
优先级较高的线程对程序更重要,处理器资源应在优先级较低的线程之前分配。但是,线程优先级不能保证线程执行的顺序,而且非常依赖平台。
创建线程。
Java提供了三种创建线程的方法:
实现Runnable接口;
继承Thread类本身;
通过Callable和Future创建线程。
通过实现Runnable接口创建线程。
创建一个线程最简单的方法是创建一个类别来实现Runnable接口。
为实现Runnable,一类只需执行一种调用Run()的方法,声明如下:
()
你可以重写这个方法,重要的是理解run()可以调用其他方法,使用其他类别,并声明变量,就像主线程一样。
在创建了实现Runnable接口的类之后,您可以在类中实例化一个线程对象。
Thread定义了几种结构方法,我们经常使用以下方法:
Thread(Runnablethreadob,stringthreadname);
在这里,threadob是实现Runable接口的类实例,threadname指定新线程的名称。
创建新线程后,您可以调用它的start()方法来运行它。
voidstart();
以下是创建线程并开始执行的例子:
实例
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会 Thread.sleep(50);
} }
catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName); t.start ();
} } }
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = ew RunnableDemo( "Thread-2");
R2.start();
} }
编译以上程序运行结果如下:
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
六、通过继承Thread来创建线程
创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
实例
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name; System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try { for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会 Thread.sleep(50);
} }
catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
} } }
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
} }
编译以上程序运行结果如下:
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
七、Thread 方法
下表列出了Thread类的一些重要方法:
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。
实例
如下的ThreadClassDemo 程序演示了Thread类的一些方法:
DisplayMessage.java 文件代码:
// 文件名 : DisplayMessage.java
// 通过实现 Runnable 接口创建线程
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) {
this.message = message;
}
public void run() {
while(true) {
System.out.println(message);
} } }
GuessANumber.java 文件代码:
// 文件名 : GuessANumber.java
// 通过继承 Thread 类创建线程
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) {
this.number = number;
}
public void run() {
int counter = 0;
int guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " guesses " + guess);
counter++;
}
while(guess != number);
System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**");
} }
ThreadClassDemo.java 文件代码:
// 文件名 : ThreadClassDemo.java
public class ThreadClassDemo {
public static void main(String [] args) {
Runnable hello = new DisplayMessage("Hello");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hello");
System.out.println("Starting hello thread...");
thread1.start();
Runnable bye = ew DisplayMessage("Goodbye");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
System.out.println("Starting goodbye thread...");
thread2.start();
System.out.println("Starting thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try {
thread3.join();
}
catch(InterruptedException e) {
System.out.println("Thread interrupted.");
}
System.out.println("Starting thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() is ending...");
} }
运行结果如下,每一次运行的结果都不一样。
Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......
八、通过 Callable 和 Future 创建线程
1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
实例
public class CallableThreadTest implements Callable<Integer> {
public static void main(String[] args) {
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++) {
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20) {
new Thread(ft,"有返回值的线程").start();
} }
try {
System.out.println("子线程的返回值:"+ft.get());
}
catch (InterruptedException e) {
e.printStackTrace();
}
catch (ExecutionException e) {
e.printStackTrace();
} }
@Override public Integer call() throws Exception {
int i = 0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
} }
九、创建线程的三种方式的对比
1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
十、线程的几个主要概念
在多线程编程时,你需要了解以下几个概念:
线程同步
线程间通信
线程死锁
线程控制:挂起、停止和恢复
十一、多线程的使用
有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
下一章节我们将介绍Java高级教程第八节:Java Applet 基础
图灵学院成立于2017年7月15日,现阶段提供 计算机基础原理、JavaSE核心、Java后端、 面试必备算法、python核心编程、数据分析、web 开发题、人工智能等专题课程,为想学习Python的学员提供优质的培训服务,帮助学员掌握更加全面的技能,是计算机人员职场中提职加薪的首选。免费java架构师视频学习地址:免费视频