多线程基础篇

111 阅读12分钟

文章目录


渊洁
我列的提纲:
参考宝典阿里手册

1.多线程解决的问题

	**渐入物联网时代,人工智能时代.日前,要解决大的吞吐量,爬取亿万级数据...提高效率不言而喻.
	据悉多核情况下,使用多线程可能会提高效率.但是在实际情况中,使用多线程的方式会额外增加系统的开销。
	相对于单核系统任务本身的资源消耗外,多线程应用还需要维护额外多线程特有的信息。
	比如,线程本身的元数据,线程调度,线程上下文的切换等。
	综上所述多线程如同,号称云计算时代最强的为解决高并发而生的go语言一样要解决的是高并发这一问题!**
	**同时没有多任务就不必要多线程**

2.详细理解多线程

(1)区分进程和线程以及管程,守护线程,用户线程

基本概念:
进程是操作系统资源分配的基本单位.具备独立的内存空间和系统资源
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

线程是处理器任务调度和执行的基本单位
常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

管程(监视器): 管程就是锁(monitor).每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针.
在这里插入图片描述

守护线程: 守护线程为用户线程服务. 当不存在用户线程时,守护线程结束.

用户线程: 用户线程之间互相独立,互不影响.

(2)多线程的并发和并行区别

并发:
如只有一个内核,四个逻辑内核.那么多线程事实上是模拟出来的.只是计算机运行足够快,感觉像是多线程.这叫并发
并行:
两内核或以上.才有真正的多线程.真正独立的各自干各自,互不影响的这叫并行.
如图
https://www.zhihu.com/question/33515481

(3)普通方法调用和多线程使用的区别:

https://www.bilibili.com/video/BV137411V7Y1?p=195

(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

https://www.cnblogs.com/guanbin-529/p/11784914.html

引用此处

与使用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)深入多线程的运行逻辑–>多线程的调度和控制

https://www.bilibili.com/video/BV137411V7Y1?p=203
在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…路漫漫其修远兮,吾将上下求索.