前言
本片文章介绍了如何创建线程以及线程的常用方法。
一、什么是线程?
线程:简单的说,就是计算机在做一件事
单线程:在计算机中同一时间只能做一件事
多线程:在计算机中同一时间可以做多件事
其实多线程在我们的生活中的使用场景很多,比如网盘的上传下载,12306的多窗口售票等等
它的主要好处有:1. 减少队列阻塞带来的影响 2. 提高CPU的利用率
二、创建线程的方式
1.继承Thread类
多线程的创建方式一:继承Thread类
1、定义一个子类继承线程类java.lang.Thread,重写run()方法
2、创建子类的对象
3、调用子类对象的start()方法启动线程(底层会自动去执行run方法)
方式一优缺点:
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
代码如下:
/*
多线程
让程序同时做多件事
多线程的创建方式一:继承Thread类
1. 定义一个子类继承线程类java.lang.Thread,重写run()方法
2. 创建子类的对象
3. 调用子类对象的start()方法启动线程(底层会自动去执行run方法)
优缺点
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
注意事项
1、启动线程必须是调用start方法,不是调用run方法。
2、直接调用run方法会当成普通方法执行,只有调用start方法才是启动一个新的线程执行。
3、不要将主线任务放在start方法之前,这样主线程一直是先跑完的,相当于是一个单线程的效果了。
扩展
对于单核cpu来讲, 多线程是一种假象
*/
public class Demo1 {
public static void main(String[] args) {
//需求:创建两个线程,分别用于打印10个A和10个B,最后观察下输出顺序
AThread aThread = new AThread();
BThread bThread = new BThread();
aThread.start();
bThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("C"+i);
}
}
}
class AThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("A"+i);
}
}
}
class BThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("B"+i);
}
}
}
2.实现Runnable接口
多线程的创建方式二:实现Runnable接口
1、定义一个线程任务类实现Runnable接口,重写run()方法
2、创建任务类对象
3、把任务类对象交给Thread处理
4、调用线程对象的start()方法启动线程
方式二的优缺点:
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。
代码如下:
/*
多线程的创建方式二:实现Runnable接口
1. 定义一个线程任务类实现Runnable接口,重写run()方法
2. 创建任务类对象
3. 把任务类对象交给Thread处理
public Thread(Runnable target)
4. 调用线程对象的start()方法启动线程
优缺点
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。
*/
public class Demo1 {
public static void main(String[] args) {
//需求:创建两个线程,分别用于打印10个A和10个B,最后观察下输出顺序
aThread aThread = new aThread();
bThread bThread = new bThread();
Thread threadA = new Thread(aThread);
Thread threadB = new Thread(bThread);
threadA.start();
threadB.start();
}
}
class aThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("A"+i);
}
}
}
class bThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("B"+i);
}
}
}
这种方式还有第二种写法(匿名内部类):
1、可以创建Runnable的匿名内部类对象。
2、再交给Thread线程对象。
3、再调用线程对象的start()启动线程。
代码如下:
public class Demo2 {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("A"+i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("B"+i);
}
}).start();
}
}
注意:
以上两种写法都有一个问题:假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。
那怎么解决这个问题呢?
JDK 5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)。
这种方式最大的优点:可以返回线程执行完毕后的结果。
3.实现Callable接口
多线程的第三种创建方式:利用Callable接口、FutureTask类来实现。
1、创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
把Callable类型的对象封装成FutureTask(线程任务对象)。
2、把线程任务对象交给Thread对象。
3、调用Thread对象的start方法启动线程。
4、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
线程创建方式三的优缺点:
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/*
多线程的创建方式三:实现Callable接口
1. 创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
把Callable类型的对象封装成FutureTask(线程任务对象)
public FutureTask<>(Callable call) 把Callable对象封装成FutureTask对象
public V get() throws Exception 获取线程执行call方法返回的结果
2. 把线程任务对象交给Thread对象。
3. 调用Thread对象的start方法启动线程。
4. 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果
优缺点
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果
缺点:编码复杂一点。
*/
public class Demo1 {
public static void main(String[] args) throws Exception {
//需求:启动两个子线程,分别计算100之内的奇数的和和偶数的和,然后在主线程中再做个汇总,得到总和
JiCall jiCall = new JiCall();
OuOddCall ouOddCall = new OuOddCall();
FutureTask<Integer> jiFutureTask = new FutureTask<>(jiCall);
FutureTask<Integer> ouFutureTask = new FutureTask<>(ouOddCall);
Thread jiThread = new Thread(jiFutureTask);
Thread ouThread = new Thread(ouFutureTask);
jiThread.start();
ouThread.start();
Integer integer = jiFutureTask.get();
Integer integer1 = ouFutureTask.get();
System.out.println(integer+integer1);
}
}
class JiCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i+=2) {
sum +=i;
}
return sum;
}
}
class OuOddCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 2; i <= 100; i+=2) {
sum +=i;
}
return sum;
}
}
三、Thread类的常用方法
Thread提供的常用方法:
public String getName() 获取当前线程的名称,线程名称默认是Thread-索引
public void setName(String name) 为线程设置名称
public static Thread currentThread() 获取当前执行的线程对象
public static void sleep(long time) 让当前执行的线程休眠多少毫秒后,再继续执行
public final void join()… 让调用当前这个方法的线程先执行完!
代码如下:
/*
Thread常见构造器
public Thread(String name) 可以为当前线程指定名称
public Thread(Runnable r) 封装Runnable对象成为线程对象
public Thread(Runnable r, String name) 封装Runnable对象成为线程对象,并指定线程名称
程对象的常见方法
public void run() 线程的任务方法
public void start() 启动线程
public String getName() 获取当前线程的名称,线程名称默认是Thread-索引
public void setName(String name) 为线程设置名称
public static Thread currentThread() 获取当前执行的线程对象
public static void sleep(long time) 让当前执行的线程休眠多少毫秒后,再继续执行
public final void join() 让调用当前这个方法的线程先执行完
*/
public class Demo1 {
//需求: 通过下面任务,我们来学习sleep和join方法
//1. 创建A、B两个线程类,分别打印1~5
//2. 在主线程序中创建A、B两个子线程
//3. 在主线程序中开启A、B两个子线程
//4. 让A线程每打印一次,都要暂停3秒钟
//5. B线程要等A线程打印完毕,再开始打印
public static void main(String[] args) throws InterruptedException {
aThread aThread = new aThread();
bThread bThread = new bThread();
aThread.setName("线程一");
bThread.setName("线程二");
aThread.start();
aThread.join();
bThread.start();
}
}
class aThread extends Thread{
@Override
public void run() {
Thread thread = Thread.currentThread();
for (int i = 1; i < 6; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(thread.getName()+i);
}
}
}
class bThread extends Thread{
@Override
public void run() {
Thread thread = Thread.currentThread();
for (int i = 1; i < 6; i++) {
System.out.println(thread.getName()+i);
}
}
}
Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会后续需要用到的时候再讲解。
总结
三种线程的创建各有优缺点,所以请选择合适的方式创建线程。