文章目录
我列的提纲:
参考宝典阿里手册
1.多线程解决的问题
**渐入物联网时代,人工智能时代.日前,要解决大的吞吐量,爬取亿万级数据...提高效率不言而喻.
据悉多核情况下,使用多线程可能会提高效率.但是在实际情况中,使用多线程的方式会额外增加系统的开销。
相对于单核系统任务本身的资源消耗外,多线程应用还需要维护额外多线程特有的信息。
比如,线程本身的元数据,线程调度,线程上下文的切换等。
综上所述多线程如同,号称云计算时代最强的为解决高并发而生的go语言一样要解决的是高并发这一问题!**
**同时没有多任务就不必要多线程**
2.详细理解多线程
(1)区分进程和线程以及管程,守护线程,用户线程
基本概念:
进程是操作系统资源分配的基本单位.具备独立的内存空间和系统资源
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
线程是处理器任务调度和执行的基本单位
常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
管程(监视器): 管程就是锁(monitor).每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针.
守护线程: 守护线程为用户线程服务. 当不存在用户线程时,守护线程结束.
用户线程: 用户线程之间互相独立,互不影响.
(2)多线程的并发和并行区别
并发:
如只有一个内核,四个逻辑内核.那么多线程事实上是模拟出来的.只是计算机运行足够快,感觉像是多线程.这叫并发
并行:
两内核或以上.才有真正的多线程.真正独立的各自干各自,互不影响的这叫并行.
如图
(3)普通方法调用和多线程使用的区别:
(4)多线程实现方法
1)继承Thread类
2)实现runnable接口
3)实现Callable接口
4)使用线程池
规则:少用继承多用实现.
1)直接继承Thread类
//Thread类也实现了runnable接口,因此必须重写run()方法
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
//然后,以下代码将创建一个线程并启动它运行:
PrimeThread p = new PrimeThread(111);
p.start();
2)实现runnable接口
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
//然后,以下代码将创建一个线程并启动它运行:
//runnable没有继承Thread类,没有start方法,无法启动线程.因此有如下操作
PrimeRun p = new PrimeRun(111);
new Thread(p).start();
3)实现Callable接口+FutureTask
与使用Runnable相比, Callable功能更强大些 实现的call()方法相比run()方法,可以返回值 方法可以抛出异常
支持泛型的返回值 需要借助FutureTask类,比如获取返回结果
Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutureTask是Futrue接口的唯一的实现类 FutureTask 同时实现了Runnable,
Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
4)使用线程池:
基本概念:它类似于JDBC连接中的连接池,常量池…
不需要每次使用时重复创建和销毁对象.
在实际开发中我们都不用前三种实现,因为资源是有限的.使用线程池可以调度资源并控制
package com.vector.mallsearch.thread;
import java.util.Optional;
import java.util.concurrent.*;
/**
* @ClassName ThreadTest
* @Description TODO
* @Author YuanJie
* @Date 2022/8/17 10:13
*/
public class ThreadTest {
// 当前系统中应当只有一两个池,每个异步任务交由线程池执行
/**
* 七大参数
* corePoolSize – the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
* maximumPoolSize – the maximum number of threads to allow in the pool
* keepAliveTime – (maximumPoolSize - corePoolSize)when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
* unit – the time unit for the keepAliveTime argument
* workQueue – the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.
* threadFactory – the factory to use when the executor creates a new thread
* handler – the handler to use when execution is blocked because the thread bounds and queue capacities are reached
*/
/**
* 工作顺序:
* 1)、线程池创建,准备好core数量的核心线程,准备接受任务
* 1.1、core满了,就将再进来的任务放入阻塞队列中。空闲的core就会自己去阻塞队列获取任务执行
* 1.2、阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
* 1.3、max满了就用RejectedExecutionHandLer拒绝任务
* 1.4、max都执行完成,有很多空闲.在指定的时间keepAliveTime以后,释放max-core这些线程
* <p>
* new LinkedBLoclingDeque<>():默认是Integer的最大值。内存不够
* <p>
* 拒绝策略: DiscardOldestPolicy丢弃最旧的任务
* AbortPolicy 丢弃新任务并抛出异常
* CallerRunsPolicy 峰值同步调用
* DiscardPolicy 丢弃新任务不抛异常
* 一个线程池core 7; max 20 , queue: 5e,100并发进来怎么分配的;
* 7个会立即得到执行,50个会进入队列,再开13个进行执行。剩下的30个就使用拒绝策略。
*/
// 创建自定义线程
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
200,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
// CompletableFuture异步编排,类似vue中promise,Docker-compose容器编排
System.out.println("main...start....");
// 无返回值
// CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
// System.out.println("当前线程池: " + Thread.currentThread().getId());
// int i = 10 / 2;
// System.out.println("运行结果: " + i);
// }, threadPoolExecutor);
// 有返回值 且进行异步编排
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程池: " + Thread.currentThread().getId());
String i = String.valueOf(10 / 2);
System.out.println("运行结果: " + i);
return i;
}, threadPoolExecutor)
// 接收上一步的结果和异常
.handle((result,error)->{
if(Optional.ofNullable(result).isPresent()){
return "success";
}
return "error";
});
String s = future.get();
System.out.println("main...end...." + s);
// 线程串行化 thenRunAsync 没法获取上一步执行结果
System.out.println("main---start---");
CompletableFuture<Void> VoidCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程池: " + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果: " + i);
return i;
}, threadPoolExecutor)
.thenRunAsync(()-> {
System.out.println("thenRunAsync()继续执行其他任务,没法获取上一步执行结果");
},threadPoolExecutor);
System.out.println("main---end---");
// 线程串行化 thenAcceptAsync 能接收上一步结果但无返回值
CompletableFuture<Void> NullCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程池: " + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果: " + i);
return i;
}, threadPoolExecutor)
.thenAcceptAsync((result)-> {
System.out.println("thenRunAsync()作为程序的最后执行结果,无返回值, i= "+ result);
},threadPoolExecutor);
// 线程串行化 thenApplyAsync 可以处理上一步结果,有返回值
System.out.println("main---start---");
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程池: " + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果: " + i);
return i;
}, threadPoolExecutor)
.thenApplyAsync((result)-> {
System.out.println("thenRunAsync()可以处理上一步结果,有返回值");
result = result*2;
return "最新的i = "+result;
},threadPoolExecutor);
System.out.println("main---end---"+stringCompletableFuture.get());
// 线程串行化 多任务组合
System.out.println("main...start....");
CompletableFuture<String> work01 = CompletableFuture.supplyAsync(()-> {
System.out.println("任务work01进行中");
return "info 1";
},threadPoolExecutor);
CompletableFuture<String> work02 = CompletableFuture.supplyAsync(()-> {
System.out.println("任务work02进行中");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "info 2";
},threadPoolExecutor);
CompletableFuture<String> work03 = CompletableFuture.supplyAsync(()-> {
System.out.println("任务work03进行中");
return "info 3";
},threadPoolExecutor);
// work01.get(); work02.get(); work03.get();.... get乃是每个线程都会被阻塞等待结果
// 而allof()是一个非阻塞等待方法
CompletableFuture<Void> allResult = CompletableFuture.allOf(work01,work02,work03);
// 等待最长的任务执行完毕后,获得最终结果
allResult.get();
System.out.println("main...end...."+work01.get()+"=>"+work02.get()+"=>"+work03.get());
// // 一个成功即可
// CompletableFuture<Object> anyResult = CompletableFuture.anyOf(work01,work02,work03);
// System.out.println("main...end...."+allResult.get());
}
}
(5)深入多线程的运行逻辑–>多线程的调度和控制
在java中,线程一共分为五种状态:创建,就绪,阻塞,运行,死亡.
创建: 即创建线程还未调用start(),还没有开始运行线程中的代码;
就绪: 线程处于可执行状态,调用start()方法,申请资源.
阻塞: (一)、等待阻塞:(二)、同步阻塞:(三)、其他阻塞:
运行: 获得cpu分配的资源.执行任务.
死亡: 正常执行或异常退出run()方法.该线程生命周期结束.
线程的调度方式:
线程终止、暂停、礼让、插队
问二:
(1)红绿灯,12306,大数据…是经典的高并发问题.
(2)什么是高并发
高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。
吞吐量:单位时间内处理的请求数量。
QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。
(3)解决高并发方案
1)提升系统的并发能力:
增强单机硬件性能 提升单机架构性能
2)水平扩展
(4)什么是多线程同步机制及其作用
在多线程的领域中,同步是一个多线程并发的情况下的特殊需求,即同步首先要求在多线程并发的情况下才能发生.
多线程同步要求:当一个线程对某内存块A进行操作时,若其他线程同时也需要对内存块A进行操作,则需要等待前一个线程操作完成后才可以进行.
同步的"同",并不是指同时进行,而更多是等待的含义,类似于有共"同"的需要而等待.
更多情况下,我们不应该对其拆分,而是应对"同步"整个词进行理解.同步可以理解为生活中的结伴行走的情形:若一位朋友因为手上抱着东西突然慢几步,另一位朋友发现后,则在前方等待一会儿,等他们同步后接过朋友手中的东西,然后一同行走.
说到线程的同步,不得不提的就是synchronized关键字.实际上,在大型应用的开发中,一旦涉及多线程的大数据处理,基本上都会用到synchronized进行同步计算.下面将以两个简单的累加运算的示例,查看在多线程的情况下,无同步和有synchronized同步的运行结果的差距.
纯手敲,引用李建平编著多线程与大数据处理实战代码:
public class RunWithoutSync{
public static int count = 0;
public static void main(String[] args){
//创建四个线程,让他们一起跑累计递增计算
Thread increaseValueThread01 =new Thread(new IncreaseWithoutSyncRunnable());
Thread increaseValueThread02 =new Thread(new IncreaseWithoutSyncRunnable());
Thread increaseValueThread03 =new Thread(new IncreaseWithoutSyncRunnable());
Thread increaseValueThread04 =new Thread(new IncreaseWithoutSyncRunnable());
increaseValueThread01.start();
increaseValueThread02.start();
increaseValueThread03.start();
increaseValueThread04.start();
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(RunWithoutSync.count);
}
}
//用于值累加递增的线程
public class IncreaseWithoutSyncRunnable implements Runnable{
@Override
public void run(){
for(int i = 0;i < 10000; i++){
RunWithoutSync.count++;
}
}
}
//该实例,按照逻辑预期结果应为40000,但由于该实例没有使用同步.因此运行结果有时会出现少于40000的情况.
改进实例.加入同步监控锁及同步关键字synchronized进行累加时候的同步控制.
纯手敲,引用李建平编著多线程与大数据处理实战代码:
public class RunWithoutSync{
public static int count = 0;
public static void main(String[] args){
//创建四个线程,让他们一起跑累计递增计算,并且加入相同的监控锁
//作为同步监控锁
Object monitorLock = new Object();
Thread increaseValueThread01 =new Thread(new IncreaseWithoutSyncRunnable(monitorLock));
Thread increaseValueThread02 =new Thread(new IncreaseWithoutSyncRunnable(monitorLock));
Thread increaseValueThread03 =new Thread(new IncreaseWithoutSyncRunnable(monitorLock));
Thread increaseValueThread04 =new Thread(new IncreaseWithoutSyncRunnable(monitorLock));
increaseValueThread01.start();
increaseValueThread02.start();
increaseValueThread03.start();
increaseValueThread04.start();
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(RunWithoutSync.count);
}
}
//用于值累加递增的线程
public class IncreaseWithoutSyncRunnable implements Runnable{
private Object monitorLock;
public IncreaseWithoutSyncRunnable(Object monitorLock){
this.monitorLock = monitorLock;
}
@Override
public void run(){
for(int i = 0;i < 10000; i++){
//同步关键字synchronized进行累加的同步控制
synchronized(monitorLock){
RunWithoutSync.count++;
}
}
}
}
//该实例,结果应为40000.
多线程锁的介绍
本文引用均标明出处.请放心食用.图片也是.
emm…路漫漫其修远兮,吾将上下求索.