Java开启多线程有三种方式:
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
- 实现Callable接口,重写call方法(juc并发包中,用的不多)
方式一:直接继承Thread类,重写run()方法
- 执行线程必须调用start()方法,从而加入到调度器中
- 不一定立即执行,系统安排调度分配执行(等待分配时间片)
- 直接调用run()方法不是开启线程,是普通方法调用
- 使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量(成员变量)
/**
* 创建多线程方法一:
* 1、创建:继承Thread + 重写run()方法
* 2、启动:创建子类对象 + start()
*/
public class StartThread extends Thread{
//多线程入口
@Override
public void run() {
for(int i = 0; i < 50; i++){
System.out.println("一边听音乐");
}
}
public static void main(String[] args) {
//创建子对象
StartThread st = new StartThread();
//启动
st.start(); //开启多线程,不保证立即调用,调度交由CPU
//st.run(); //调用普通方法,正常的逻辑顺序流
for(int i = 0; i < 50; i++){
System.out.println("一边coding");
}
}
}
打开start()原码,会发现start()会调用本地方法start0(),再本地方法start0()中会调用run()方法。
方式二:实现Runnable接口,重写run()方法
- java中存在单继承的局限性,因此建议多采用实现,少用继承,也就是多用Runable接口
- 方便共享资源,一份资源多个代理
- Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
/**
* 创建多线程方法二:
* 1、创建:实现Runnable中的run()方法
* 2、启动:创建实现类对象 + 代理类Thread对象 + start()
*/
public class StartRun implements Runnable{
//多线程入口
@Override
public void run() {
for(int i = 0; i < 50; i++){
System.out.println("一边听音乐");
}
}
public static void main(String[] args) {
//创建实现类对象
StartRun sr = new StartRun();
//创建代理类对象
Thread t = new Thread(sr);
//启动
t.start(); //开启多线程,不保证立即调用,调度交由CPU
for(int i = 0; i < 50; i++){
System.out.println("一边coding");
}
}
}
由于sr和t只用了一次,所以建议采用匿名类
public class StartRun implements Runnable{
//多线程入口
@Override
public void run() {
for(int i = 0; i < 50; i++){
System.out.println("一边听音乐");
}
}
public static void main(String[] args) {
new Thread(new StartRun()).start();
for(int i = 0; i < 50; i++){
System.out.println("一边coding");
}
}
}
针对方法二中,一份资源,多份代理,实现资源共享,这里以一个简单地实例说明
- ctrl + alt + t 快速加上try-catch代码
/**
* 资源共享,并发编程(需要保证线程安全,后续讲解)
* ctrl + alt + p 快速加上try-catch代码
*/
public class Web12306 implements Runnable {
private int ticketNumbs = 10;
@Override
public void run() {
while(true) {
if (ticketNumbs <= 0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNumbs--);
}
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
Web12306 web = new Web12306();
new Thread(web,"码畜").start();
new Thread(web,"码农").start();
new Thread(web,"码神").start();
}
}
方式三:使用Callable和Future创建线程
- juc并发包下实现Callable接口,重写call()方法
- 据说工作几年之后才会接触,可以了解下用于面试
import java.util.concurrent.*;
/**
* 创建线程的方式三
* 创建:实现Callable中的call()方法
* 启动:创建执行服务 ExecutorService service = Executors.newFixedThreadPool(1);
* 提交结果 Future<T> result = service.submit(tc);
* 获取结果 T r = result.get();
* 关闭服务 service.shutdownNow();
*/
public class ThreadCall implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for(int i = 0; i < 20; i++){
System.out.println(Thread.currentThread().getName() + "-->" + "听音乐");
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadCall tc = new ThreadCall();
ThreadCall tc2 = new ThreadCall();
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(2);
//提交结果
Future<Boolean> result = service.submit(tc);
Future<Boolean> result2 = service.submit(tc2);
//获取结果
boolean r = result.get(); //boolean根据泛型指定的类型变
boolean r2 = result2.get();
//关闭服务
service.shutdownNow();
}
}
补充:阅读疯狂java讲义,总结一下方式三的一些知识点
前面方式一和方式二都是将run()方法作为线程执行体,那么可否将任一方法包装成线程执行体,java当前不行(C#可以),但是为了更为方便的进行多线程编程,从Java 5开始提供Callable接口,可以说是Runnable接口的增强版。
主要特点:
- call()方法可以有返回值
- call()方法可以声明抛出异常
疯狂java讲义中使用Callable接口的方式与上述方式不太一样,此书中的方式基本与方式二一致,也是使用Thread代理,只不过在Runnable中直接代理Runnable对象,这里需要使用FutureTask类包装一下,但实质一样。与上述写法明显不同,我想可能是存在不用的使用方式,有时间再去探究juc编程。
继承Thread类方式 | 实现Runnable or Callable方式 | |
优点 | 编写简单,如需访问当前线程,只需使用this即可 | 1、还可以继承其他类 2、在这种方式下,多个线程可以共享同一个target对象,也就是一份资源,多个代理,从而可以将CPU、代码和数据分开 |
缺点 | 已经继承了Thread类了,无法继承其他类 | 编程相对复杂些,访问当前线程必须使用Thread.currentThread() |
总结 | 推荐采用实现Runnable or Callable方式来创建多线程 |
最后附上书中方式三写法:
package com.ly.thread;
import java.util.concurrent.FutureTask;
/**
* liyang 2020-08-11
*
*/
public class ThirdThread {
public static void main(String[] args) {
//创建Callable对象
//ThirdThread rt = new ThirdThread();
//先使用Lambda表达式创建Callable<>对象
//再使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<>(() -> {
int i = 0;
for(; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "的循环变量i的值: " + i);
}
return i; //call()方法可以有返回值
});
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) {
e.printStackTrace();
}
}
}
运行结果:交替执行主线程和子线程,以下结果仅仅作为显示,因为各打印100行,太占篇幅
main的循环变量i的值: 0
main的循环变量i的值: 1
main的循环变量i的值: 2
...
有返回值的线程的循环变量i的值: 0
有返回值的线程的循环变量i的值: 1
...
有返回值的线程的循环变量i的值: 99
main的循环变量i的值: 99
子线程的返回值:100
Process finished with exit code 0