Java怎么支持多线程,何时需要多线程,如何创建(多)线程
Java多线程
1、引入
1、何时需要多线程:
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
需要一些后台运行的程序时。
2、多线程
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread 类来体现。
Thread类的特性 :
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
线程的创建和使用Thread类
2、API中创建线程的方式
1、继承Thread类
1、方法介绍
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
2、实现步骤
1、 定义子类继承Thread类。
2、 子类中重写Thread类中的run方法。
3、 创建Thread子类对象,即创建了线程对象。
4、 调用线程对象start方法:启动线程,调用run方法。
注意
1、想要启动多线程,必须调用start方法。,只有使用start方法才能启动一个线程.
2、如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
3、run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
4、一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。
3、案例
public class Demo1 {
public static void main(String[] args) {
//创建线程对象
PrintWorld printWorld = new PrintWorld();
PrintThread printThread = new PrintThread();
//两个线程开始工作,开始争夺系统资源
printWorld.start();
printThread.start();
}
}
//打印:你好世界
class PrintWorld extends Thread{
@Override
public void run() {
//要将这个线程执行的代码写入run方法中
for (int i = 1; i <= 100; i++) {
System.out.println("**第"+i+"次:你好世界!");
}
}
}
//打印:线程你好
class PrintThread extends Thread{
@Override
public void run() {
//要将这个线程执行的代码写入run方法中
for (int i = 1; i <= 100; i++) {
System.out.println("--第"+i+"次:线程你好!");
}
}
}
2、实现Runnable接口
1、方法介绍
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
Thread(Runnable target, String name) | 分配一个新的Thread对象 |
2、实现步骤
1、 定义子类,实现Runnable接口。
2、 子类中重写Runnable接口中的run方法。
3、 通过Thread类含参构造器创建线程对象。
4、 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5、 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
3、案例
练习: 利用实现接口的方式创建两个线程,其中一个线程 打印 1000以内的奇数 另一个线程打印 1000以内所有的偶数
public class Demo2 {
public static void main(String[] args) {
//创建两个任务类对象
EvenNumber evenNumber = new EvenNumber();
OddNumber oddNumber = new OddNumber();
//创建两个线程对象,Thread类 将两个任务对象作为参数 传递给线程对象 让线程对象 去执行这个任务
Thread t1 = new Thread(oddNumber);
Thread t2 = new Thread(evenNumber);
//启动两个线程
t1.start();
t2.start();
}
}
//打印1000以内奇数
class OddNumber implements Runnable{
@Override
public void run() {
//这个任务类 要完成的任务代码
for (int i = 1; i <=1000 ; i+=2) {
System.out.println("=============="+i+"===============");
}
}
}
//打印1000以内偶数
class EvenNumber implements Runnable{
@Override
public void run() {
//这个任务类 要完成的任务代码
for (int i = 0; i <=1000 ; i+=2) {
System.out.println("+++++++++++++++"+i+"+++++++++++++++++++");
}
}
}
3、实现Callable接口
1、方法介绍
方法名 | 说明 |
---|---|
V call() | 计算结果,如果无法计算结果,则抛出一个异常 |
2、FutureTask类中的方法
构造方法:
FutureTask(Callable<V> callable)创建一个 FutureTask,一旦运行就执行给定的 CallableV
成员方法:
get()如有必要,等待计算完成,然后获取其结果
3、实现步骤
1、定义一个子类MyCallable实现Callable接口
2、在子类MyCallable中重写call()方法
3、创建子类MyCallable的对象
4、创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
5、创建Thread类的对象,把FutureTask对象作为构造方法的参数
6、启动线程
7、再调用get方法,就可以获取线程结束之后的结果。
4、案例
//案例一:
package com.offcn.day21;
import java.io.FileNotFoundException;
import java.util.concurrent.*;
public class Demo9 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建两个任务类对象
OddNumber oddNumber = new OddNumber();
EvenNumber evenNumber = new EvenNumber();
// Callable接口的实现类对象 是不能直接提交给线程对象的必须要作为参数传递给 FutureTask 再将FutureTask对象作为参数提交线程对象
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(oddNumber);
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(evenNumber);
Thread t1 = new Thread(futureTask1);
Thread t2 = new Thread(futureTask2);
t1.start();
t2.start();
Integer integer = futureTask1.get();
Integer integer1 = futureTask2.get();
System.out.println(integer+ integer1);//这个结果 必须要等到两个线程都结束才能获取
// 方法二
// ExecutorService executorService = Executors.newFixedThreadPool(2);
// Future<Integer> submit = executorService.submit(ji);
// Future<Integer> submit1 = executorService.submit(ou);
// Integer integer = submit.get();
// Integer integer1 = submit1.get();
// System.out.println(integer + integer1);
// executorService.shutdown();
}
}
class OddNumber implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <=100 ; i+=2) {
sum += i;
System.out.println(i);
}
return sum;
}
}
class EvenNumber implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 2; i <=100 ; i+=2) {
sum += i;
System.out.println(i);
}
return sum;
}
}
案例: 创建 两个任务对象:
任务对象1: 任务是 拼接 所有的小写字母 abcdefg .....z
任务对象2: 任务是 拼接 所有的大写字母 A....Z
最后将两个字符串进行拼接 打印输出
import java.util.concurrent.*;
public class Demo4 {
public static void main(String[] args) throws Exception{
// 采用第一种方式
LowerCase lowerCase = new LowerCase();
UpperCase upperCase = new UpperCase();
//线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//为线程池提交任务
Future<StringBuilder> submit1 = pool.submit(lowerCase);
Future<StringBuilder> submit2 = pool.submit(upperCase);
StringBuilder stringBuilder1 = submit1.get();
StringBuilder stringBuilder2 = submit2.get();
System.out.println(stringBuilder1.append(stringBuilder2));
pool.shutdown();
// 方式二
// FutureTask<StringBuilder> f1 = new FutureTask<>(lowerCase);
// FutureTask<StringBuilder> f2 = new FutureTask<>(upperCase);
//
// //创建两个线程
// Thread t1 = new Thread(f1);
// Thread t2 = new Thread(f2);
//
// t1.start();
// t2.start();
//
// //接收 两个任务完成之后的 结果
// StringBuilder stringBuilder1 = f1.get();
// StringBuilder stringBuilder2 = f2.get();
//
// StringBuilder append = stringBuilder1.append(stringBuilder2);
// System.out.println(append);
}
}
class LowerCase implements Callable<StringBuilder>{
@Override
public StringBuilder call() throws Exception {
StringBuilder stringBuilder = new StringBuilder();
for (char i = 'a'; i <= 'z' ; i++) {
stringBuilder.append(i);
}
return stringBuilder;
}
}
class UpperCase implements Callable<StringBuilder>{
@Override
public StringBuilder call() throws Exception {
StringBuilder stringBuilder = new StringBuilder();
for (char i = 'A'; i <= 'Z' ; i++) {
stringBuilder.append(i);
}
return stringBuilder;
}
}
4、继承方式和实现方式的联系与区别
1、区别
1、继承Thread:线程代码存放Thread子类run方法中。
2、实现Runnable、Callable:线程代码存在接口的子类的run方法。
2、实现方式的好处
1、避免了单继承的局限性
2、多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
3、匿名内部类创建线程
1、继承Thread类的形式
案例:
public class Demo11 {
public static void main(String[] args) {
//局部内部类
//定义一个类 继承 Thread 类
//父类引用指向子类对象
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}.start();
}
}
2、实现Runnable接口形式
案例:
public class Demo12 {
public static void main(String[] args) {
//创建任务类对象
//接口引用指向实现类对象
//创建线程对象
// new Thread( new Runnable(){
//
// @Override
// public void run() {
// for (int i = 0; i < 100; i++) {
// System.out.println(i);
// }
// }
// }).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}).start();
//写成 lambda表达式
// new Thread(()->{
// for (int i = 0; i < 100; i++) {
// System.out.println(i);
// }
// }).start();
}
}
3、实现Callable接口形式
案例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Demo11 {
public static void main(String[] args) {
//创建任务对象
//接口引用指向实现类对象
new Thread(new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return null;
}
})).start();
}
}
4、线程池创建线程
1、概述
1、概念
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。
那么什么是线程池呢?
线程池也是可以看做成一个池子,在该池子中存储很多个线程。
2、意义
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,
当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。
针对这一种情况,为了提高性能,我们就可以采用线程池。
线程池在启动的时候,会创建大量空闲线程;
当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。
等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态,等待下一次任务的执行。
2、创建线程池
1、创建线程池——Executors
1、概述
概述 : JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。
我们可以使用Executors中所提供的静态方法来创建线程池
1.static ExecutorService newCachedThreadPool() 创建一个默认的线程池
2.static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
3.static ExecutorService newSingleThreadExecutor()创建一个使用从无界队列运行的单个工作线程的执行程序。
ExecutorService :
<T> Future<T> submit(Callable<T> task)
提交值返回任务以执行,并返回代表任务待处理结果的Future。
Future<?> submit(Runnable task)
提交一个可运行的任务执行,并返回一个表示该任务的未来
案例:
//案例1:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo5 {
public static void main(String[] args) {
//获取一个线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//只有一条线程
Runnable runnable1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的====================="+i);
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的!!!!!!!!!!!!!!!!!!!!!!!!!!!"+i);
}
}
};
executorService.submit(runnable1);//提交了一次
executorService.submit(runnable2);
executorService.shutdown();
}
}
//案例2:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo6 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
//只有一条线程
Runnable runnable1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的====================="+i);
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的!!!!!!!!!!!!!!!!!!!!!!!!!!!"+i);
}
}
};
Runnable runnable3 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的::::::::::::::::::::::::::::::::"+i);
}
}
};
Runnable runnable4 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的***********************************"+i);
}
}
};
executorService.submit(runnable1);//提交了一次
executorService.submit(runnable2);
executorService.submit(runnable3);
executorService.submit(runnable4);
executorService.shutdown();
}
}
2、创建线程池——ThreadPoolExecutor
1、概述
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
创建一个新 ThreadPoolExecutor给定的初始参数。
2、TheadPoolExecutor原理介绍以及任务拒绝策略介绍
2.1、参数详解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
2.2、线程池-非默认任务拒绝策略
概述
概述: RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
注意事项
注意事项: 明确线程池对多可执行的任务数 = 队列容量 + 最大线程数
案例:
//案例
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo10 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3 , 5 , 1L , TimeUnit.SECONDS , new ArrayBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()
);
//通过循环 来给线程池提交任务
for (int i = 1; i <= 9 ; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在办理业务");
}
});
}
threadPoolExecutor.shutdown();
}
}