多线程的四种实现方式
一、多线程概念
1.进程与线程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少 有一个线程。线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分 成若干个线程。
2.线程调度
线程调度分为分时调度和抢占式调度。
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java使用的为 抢占式调度。 CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时 刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使 用率更高
3.同步与异步
同步:排队执行 , 效率低但是安全.
异步:同时执行 , 效率高但是数据不安全.
4.并发与并行
并发:指两个或多个事件在同一个时间段内发生。 一秒并发,一分钟并发,一小时并发
并行:指两个或多个事件在同一时刻发生(同时发生)
二、多线程实现方式
1.继承Thread类
代码如下:
public class Demo1 {
public static void main(String[] args) {
MyThread m = new MyThread();
//启动线程
m.start();
}
public static class MyThread extends Thread{
@Override
public void run() {
//这个执行路径的方式,不是调用run方法,
//而是通过thread对象的start()来启动任务
for(int i=0;i<10;i++){
System.out.println("哈哈哈"+i);
}
}
}
}
使用一次的类可用匿名内部类简化代码
public class Demo1 {
public static void main(String[] args) {
new MyThread() {
//重写run方法,
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println( "哈哈哈" + i);
}
}
}.start();
}
}
步骤
- 创建一个继承于Thread类的子类
- 重写Thread类中的run():将此线程要执行的操作声明在run()
- 创建Thread的子类的对象
- 调用此对象的start():①启动线程 ②调用当前线程的run()方法
2.实现 Runnable接口
代码如下:
public class Demo2 {
public static void main(String[] args) {
//1. 创建一个任务对象
MyRunnable r = new MyRunnable();
//2. 创建一个线程,并为其分配一个任务
Thread th = new Thread(r);
//3. 执行这个线程
th.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("哈哈哈"+i);
}
}
}
}
步骤
- 创建一个实现Runnable接口的类
- 实现Runnable接口中的抽象方法:run():将创建的线程要执行的操作声明在此方法中
- 创建Runnable接口实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 调用Thread类中的start():① 启动线程 ② 调用线程的run() —>调用Runnable接口实现类的run()
与继承Thread相比,实现Runnable的优势
- 通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况
- 可以避免单继承所带来的局限性
- 任务与线程是分离的,提高了程序的健壮性
- 后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
以下两种方式是jdk1.5新增的
3.实现Callable接口
Runnable 与 Callable 对比
Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable 与 Callable的不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。
代码如下:
public class Demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1. 编写类实现Callable接口 , 实现call方法
Callable<Integer> c = new MyCallable();
//2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> task = new FutureTask<>(c);
//3. 通过Thread,启动线程
new Thread(task).start();
//4. 可返回执行结果
Integer j = task.get();
System.out.println("返回值:"+j);
}
static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i=0;i<10;i++){
System.out.println("哈哈哈"+i);
sum=sum+i;
}
return sum;
}
}
}
4.线程池创建
4.1 缓存线程池
说明
- 缓存线程池无限制长度
- 任务加入后的执行流程:1判断线程池是否存在空闲线程 2存在则使用 3不存在则创建线程并使用
代码如下:
public class Demo4 {
public static void main(String[] args) {
//向线程池中加入新的任务
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈哈");
}
});
}
}
4.2 定长线程池
说明
- 长度是指定的线程池
- 加入任务后的执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
代码如下:
public class Demo5 {
//定长线程池
public static void main(String[] args) {
//参数指定长度
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈");
}
});
}
}
4.3 单线程线程池
执行流程
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用
代码如下:
public class Demo6 {
public static void main(String[] args) {
//单线程线程池
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "哈哈");
}
});
}
}
4.4 周期定长线程池
执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
代码如下:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo7 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
/*
周期性执行任务
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(没隔多久执行一次)
参数4:时长数字的单位
*/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈");
}
},5,1,TimeUnit.SECONDS); //5秒后每1秒执行一次
}
}