文章目录
+比如有三个计算,耗时如下
![image.png](https://i-blog.csdnimg.cn/blog_migrate/5a550ed50c1e835df726fc619736058f.png)
单核CPU,要顺序执行,就是10+11+9+1=31ms
而加入有个四核CPU,执行用时就是11+1=12ms
案例验证
有一个数组,容量一亿,都是1,分别用单线程和多线程求和一亿次。
原数组
C方法:
创建了四个线程,每个线程计算2500万个数,最终把结果汇总起来
D方法:
一个线程计算一亿个数
结果:在本机上(本机为多核CPU,可以提现多线程比单线程好)
C方法速度快
在Linux上(Linux是单线程的,多线程单线程都一样)
结论
单核CPU中多线程不能实际提高程序运行效率,但是可以让CPU在不同线程切换,不至于一个线程总占用CPU。
以上是线程的应用,接下来是线程的基础知识
线程基础知识
创建和运行线程
每个Java程序一启动的时候就创建了一个线程,这个线程就是主线程main。
如果在主线程之外还想再创建线程,可以用如下方法
方法一:用Thread对象创建和启动线程(继承Thread)
创建一个Thread的子类,重写run方法,run方法中是线程要执行的任务代码,
这样只是创建了Java中的线程对象,还没有和操作系统的线程相关联
还需要启动线程,调用Thread的start方法。
package com.zqh.day1;
import lombok.extern.slf4j.Slf4j;
/**
* 使用Thread类创建线程
*/
@Slf4j(topic = "c.CreateThread1")
public class CreateThread1 {
// Java 启动时默认使用的主线程
public static void main(String[] args) {
// 除了主线程,还要创建线程时,用Thread对象
Thread t = new Thread(){
@Override
public void run() {
// t线程执行的代码
log.debug("t running");
}
};
// 为线程设置线程名
t.setName("t1");
// 启动线程
t.start();
// 主线程的输出
log.debug("main running");
}
}
方法二:使用Runnable配合Thread
这种方法就是把创建线程和线程执行的任务这两步给分开了
【Thread:线程】
【Runnable:任务】
Runnable源码:
package com.zqh.day1;
import lombok.extern.slf4j.Slf4j;
/**
* 通过Runnable+Thread创建线程,把任务和线程分开
* 更为灵活
*/
@Slf4j(topic = "c.CreateThread2")
public class CreateThread2 {
// 主线程
public static void main(String[] args) {
// 创建Runnable对象(任务对象)
Runnable r = new Runnable() {
// 任务
@Override
public void run() {
log.debug("task running");
}
};
// 线程对象,把任务对象作为参数传进构造方法,第二个参数为线程名
Thread t = new Thread(r,"t2");
// 启动线程
t.start();
}
}
执行结果
只有一个抽象方法的接口可以用lambda简化
简化流程:
// 创建Runnable对象(任务对象)
Runnable r = new Runnable() {
// 任务
@Override
public void run() {
log.debug("task running");
}
};
变成
// 创建Runnable对象(任务对象)
Runnable r = ()->{
log.debug("task running");
};
方法一和方法二的区别
Runnable会给Thread传一个target,如果target不为空,就target.run();
走的都是run方法
用Runnable可以与高级API配合,更灵活
方法三:FutureTask配合Thread
FutureTask可以获取任务的执行结果
public interface RunnableFuture<V> extends Runnable, Future<V>
可以看到,FutureTask继承了Runnable,也可以当做一个任务对象,那么与Runnable不同的是,还多继承了一个Future接口
Future接口是用来返回执行结果的。可以在两个线程之间,把一个结果传给另外一个线程。
FutureTask对象的创建还需要配合一个Callable类型的参数来创建。
Callable源码
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
可以看到,和Runnable很像,但是他可以返回结果和抛出异常
这是Runnable,不能返回结果和抛异常
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
FutureTask+Thread创建线程代码:
package com.zqh.day1;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j
public class CreateThread3 {
// 主线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建可返回结果+抛异常的任务对象FutureTask
// 可以有泛型,泛型就是返回结果的类型
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
// Callable 的call方法,里面是线程具体执行的任务
@Override
public Integer call() throws Exception {
log.debug("callable is running");
// 休眠一秒钟返回结果100
Thread.sleep(1000);
return 100;
}
});
/*
还是得借助Thread执行这个线程,把task传给Thread的构造方法
因为FutureTask也实现了Runnable,所以可以作为参数传给Thread
*/
Thread t = new Thread(task,"t3");
t.start();
// 线程返回了100,但是主线程还没用到,现在用主线程打印t线程的返回值
// 用task任务对象的get方法,主线程走到这里时会等待t线程的结果返回
// 所以走到这里会等待1秒
log.debug("{}",task.get());
}
}
观察多个线程同时执行
t1和t2都是死循环执行输出running这句话
执行后:
交替执行。