谈起java多线程,我们首先想到的肯定是如何实现java多线程。下面我们就来聊一聊java多线程的实现方式。
一、java多线程的实现方式
1、继承Thread类,重写run方法;
2、实现Runnable接口,实现run方法;
3、实现Callable接口,实现call()方法;
具体用法如下:
第一步:创建Callable接口的实例化子对象;
第二步:将Callable对象传入FutureTask构造函数中,创建FutureTask对象;
第三步:将FutureTask对象传入Thread的构造函数中,创建Thread对象;
第四步:调用start()方法,启动线程;
代码实例:
package com.javabase.multithread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* @author
* @date 2020/12/19
* java中实现多线程的三种方法;
* 四中线程池的比较;
* 如何自定义并使用线程池
*/
public class MultiThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//(1)继承Thread类型,重写run方法
//new Thread1().start();
//(2)实现了Runnable接口,实现run方法
//new Thread(new Thread2()).start();
//(3)实现Callable接口,实现call方法;
// ExecutorService来使用——推荐用法
ExecutorService executorService = Executors.newCachedThreadPool();
Thread3 thread3 = new Thread3();
Future<Integer> future = executorService.submit(thread3);
//停止接收新任务,原来的任务继续执行
executorService.shutdown();
//get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
//get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null
Integer result = future.get();
System.out.println(result);
//使用Thread
FutureTask<Integer> futureTask = new FutureTask<>(thread3);
Thread thread4 = new Thread(futureTask);
thread4.start();
System.out.println("使用Thread的方式获取结果===="+futureTask.get());
}
}
package com.javabase.multithread;
/**
* @author
* @date 2020/12/19
* 继承Thread类型,重写run方法
*/
public class Thread1 extends Thread {
@Override
public void run() {
System.out.println("线程1启动");
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1继承了Thread类,一直在工作。。。。。。。。");
}
}
}
package com.javabase.multithread;
/**
* @author
* @date 2020/12/21
* 实现Runnable接口,实现run方法
*/
public class Thread2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("线程2启动");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2实现了Runnable接口,重写了run方法,一直在工作");
}
}
}
package com.javabase.multithread;
import java.util.concurrent.Callable;
/**
* @author
* @date 2020/12/21
* 实现Callable接口,实现call方法;
* 接口中的泛型就是返回的结果类型
*/
public class Thread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("线程3开始启动。。。。。。");
Thread.sleep(10000);
int sum =0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
二、通过线程池的方式实现多线程,这也是在java开发项目中最常用的方法。
按道理说,我们前面已经学习了三种创建多线程的方法,为什么在项目中还要使用线程池的方法来创建线程呢?主要是因为这种方式有如下优点:
线程池:顾名思义就是存放线程的池子,系统已经把线程创建好了,使用的时候直接拿就好了。其实多线程的创建与销毁是比较消耗资源的,使用线程池减小了线程创建与销毁的开销。
线程池既是存放线程的地方,也对线程的生命周期进行管理。
既然谈到了线程池,我们不得不说常用的四种线程池类型了。
(1)缓存类型的线程池(newCachedThredPool):线程池可以根据任务的多少,自动控制线程的数量,还是挺爽的用法。
//创建缓存类型的线程池:
//线程池会根据任务的多少自动控制线程数量
ExecutorService executorService = Executors.newCachedThreadPool();
缺点:线程池中最大线程数量为Integer.MAX_VALUE,会造成OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(2)固定数量的线程池(newFixedThreadPool):创建固定数量的线程,放在池子里面,当任务数量多余线程数量时,需要排队;少于的时候就会造成资源浪费。
//创建100个线程
ExecutorService executorService1 = Executors.newFixedThreadPool(100);
缺点:使用LinkedBlockingQueue,这是无界队里,可能会造成OOM。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
(3)创建可以定时或者延时执行的线程池(newScheduledThreadPool):
//long initialDelay:表示延迟时间
//long period:表示运行次数
//TimeUnit unit:表示时间单位
//创建的时候需要传入创建线程的数量
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(100);
//表示延迟11s,每11s执行一次,输出"实现了多线程"
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
System.out.println("实现了多线程");
}
}, 1, 11, TimeUnit.SECONDS);
缺点:线程池中最大线程数量为Integer.MAX_VALUE,会造成OOM。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
(4)创建单例线程池(newSingleThreadExecutor):它只会创建一个线程池,用这个线程池来完成任务。
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
缺点:使用LinkedBlockingQueue,这是无界队里,可能会造成OOM。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
三、推荐创建线程的方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
各个字段的含义:
(1)corePoolSize:核心线程数
(2)maximumPoolSize:最大线程数
(3)keepAliveTime:线程空闲存活时间
(4)unit:线程空闲存活时间单位
(5)workQueue:任务排队队列
(6)threadFactory:线程生产工厂
(7)handler:线程拒绝策略
线程的四中拒绝策略
(1)rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
(2) rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
(3)rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
(4) rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
四、多线程的状态转换过程
五、补充
线程的优先级—priority
优先级不严格代表执行顺序,只是代表执行线程的概率大小。
死锁形成的原因:
1、资源的互斥:甲拥有了A资源后,乙无法在拥有A资源;
2、不可剥夺性:甲拥有了A资源后,乙无法从甲那里争夺到A资源;
3、请求保持:甲拥有了A资源之后,又去请求B资源;在获取不到B资源的情况下,依然保持对A资源的拥有;
4、循环等待:甲获得了A资源,还缺B资源;乙获得了B资源,还缺A资源;