并发编程:创建线程
Java程序总是从一个静态且无返回值的 main() 方法开始从上至下依次执行。在不添加其它线程情况下,程序执行时会占用一个CPU进程,且这个进程中只包含一条主线程。在Java中,创建线程首先要定义线程类,然后将线程类实例化为线程对象后等待后续调用。实现这个过程有三种方式。
一、继承 Thread 类
java.lang.Thread:Java中用于描述线程的类。创建线程的方法之一便是自定义一个类继承 Thread 类。然后重写 Thread 类提供的 run() 方法来设置线程任务。
1. 实现步骤
- 定义一个继承 Thread 类的线程类
- 在线程类中重写 Thread 类提供的 run() 方法,设置线程任务(开启新线程后要做什么?)
- 实例化线程类
- 调用 Thread 类提供的 start() 方法,启动新线程,执行 run() 方法
2. 相关方法
- void start(): 使目标线程开始执行;Java虚拟机调用目标线程的 run() 方法,实现双线程并发执行:即当前线程(main线程)和另一个线程(实例化的自定义线程对象,执行其 run() 方法)
- String getName():获取线程名称
- public 自定义线程类 currentThread(): 获取当前正在执行的线程对象的引用
- public static void sleep(long millis): 使当前正在执行的线程以指定的毫秒数暂停一段时间
通过继承 Thread 类创建线程的示例:
// 1.定义一个Thread类的子类
class MyThread extends Thread {
public MyThread() { }
public MyThread(String name) {
super(name);
}
// 2.在Thread类的子类中重写 run() 方法,设置线程任务(开启线程要做什么?)
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1); // 使用Thread.sleep增加线程阻塞几率
} catch (InterruptedException e) {
e.printStackTrace(); // sleep() 方法需要处理中断异常
}
// 打印当前线程名称的两种方式
// System.out.println(Thread.currentThread().getName());
// 通过Thread类的静态方法调用线程名
System.out.println(this.getName() + ": " + i);
// 由于继承了Thread类,因此可以使用this直接调用getName()方法
}
}
}
class DemoThread {
public static void main(String[] args) {
// 3.实例化Thread子类对象
MyThread mt1 = new MyThread();
// 4.调用Thread类中的 start() 方法,启动新线程,执行 run() 方法
mt1.start();
MyThread mt2 = new MyThread();
// 4.调用Thread类中的 start() 方法,启动新线程,执行 run() 方法
mt2.start();
// 主线程方法
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main: " + i);
}
}
}
运行结果
Thread-1: 0
main: 0
Thread-0: 0
Thread-1: 1
main: 1
Thread-0: 1
Thread-1: 2
main: 2
Thread-0: 2
Thread-1: 3
main: 3
Thread-0: 3
……
二、实现 Runnable 接口
java.lang.Runnable:Runnable 接口提供了另一种创建线程类的途径,Runnable 的实现类可以在不继承 Thread 类的情况下实例化一个线程实例。通常情况下,如果只是通过重写 run() 方法来设置线程任务,而不打算修改过增强 Thread 类中的相关方法的话,那么通过实现 Runnable 接口就可以满足需求。
1. 实现步骤
- 定义一个 Runnable 接口的实现类
- 在实现类中重写 Runnable 接口的 run() 方法,设置线程任务
- 实例化一个 Runnable 接口的实现类对象 target
- 通过 Thread 类的构造方法 Thread(Runnable target) 构造出线程对象
- 调用 Thread 类对象的 start() 方法,开启新的线程执行 run() 方法
tips:Runnable 对象仅仅作为 Thread 对象的 target,Runnable 实现类里包含的 run() 方法仅作为线程执行体。
而实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执行其 target 的 run() 方法。
2. 相关方法
- Thread(Runnable target):通过传入一个 Runnable 接口的实现类来分配线程对象
- Thread(Runnable target, String name):重载方法,传入一个 Rnnable 接口的实现类和一个线程名来分配新的线程对象
// 1.定义一个Runnable接口的实现类
class RunnableImpl implements Runnable{
@Override
public void run() {
// 2.在实现类中重写Runnable接口的 run() 方法,设置线程任务
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
class DemoRunnable {
public static void main(String[] args) {
// 3.实例化一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
// 4.实例化一个Thread类对象,向构造方法中传递Runnable接口的实现类对象
Thread thread = new Thread(run); // 开启子线程
// 5.调用Thread类对象的 start() 方法,开启新的线程执行 run() 方法
thread.start();
new Thread(new RunnableImpl()).start(); // 传递不同的实现类,实现不同的任务
// 主线程
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
运行结果:
main: 0
main: 1
Thread-0: 0
Thread-1: 0
main: 2
Thread-0: 1
Thread-1: 1
Thread-0: 2
Thread-1: 2
main: 3
Thread-0: 3
main: 4
Thread-1: 3
Thread-0: 4
……
3. Runnable 接口与 Thread 类对比
实际上所有的多线程代码都是通过运行 Thread 的 start() 方法来运行的。因此,不管是继承 Thread 类还是实现
Runnable 接口来实现多线程,最终还是通过 Thread 的对象的 API 来控制线程的,熟悉 Thread 类的 API 是进行多线程编程的基础。
实现Runnable接口比继承Thread类所具有的优势在于:如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。 具体可以概括为以下几点
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,将设置线程和启动线程分离,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用
java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进
程。
三、匿名线程
线程对象也可以使用线程的内匿名内部类方式创建和启动,可以方便的实现每个线程执行不同的线程任务操作。使用匿名内部类实现线程对象的优势在于减少了代码量,并且将许多流程合并:
- 将 Thread 类继承、重写 run() 方法、继承类实例化、线程启动一步完成
- 将 Runnable 接口实现、重写 run() 方法、实现类实例化、Thread 对象实例化、线程启动一步完成
1. 继承 Thread 类的匿名线程
class DemoUnnamedThread {
public static void main(String[] args) {
// Thread-0
new Thread() {
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println(this.getName() + "-->" + i);
}
}
}.start();
// Thread-1
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
).start();
// main
for (int i = 0; i < 2000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
2. 实现 Runnable 接口的匿名线程
class DemoUnnamedRunnable {
public static void main(String[] args) {
Runnable run = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
};
new Thread(run).start(); // 实例化线程对象也可以合并到上面
// 主线程
for (int i = 0; i < 2000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}