并发系列之初识多线程

1、什么是多线程?

1.1、线程和进程

1.1.1、进程

       当一个程序被运行,就开启了一个进程, 比如启动了qq,word
       程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备

1.1.2、线程

       一个进程内可分为多个线程
       一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令

操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

外部链接:通俗易懂的进程与线程之间的关系(有图)


1.2、并行和并发

1.2.1、并行

       多核cpu运行 多线程时,真正的在同一时刻运行

1.2.2、并发

       单核cpu运行多线程时,时间片进行很快的切换。线程轮流执行cpu
在这里插入图片描述
在这里插入图片描述


2、为什么使用多线程?

多线程的好处:

  1. 程序运行的更快!快!快!
  2. 充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力
    在这里插入图片描述

3、多线程难在哪里?

       单线程只有一条执行线,过程容易理解,可以在大脑中清晰的勾勒出代码的执行流程
       多线程却是多条线,而且一般多条线之间有交互,多条线之间需要通信,一般难点有以下几点:
              1. 多线程的执行结果不确定,受到cpu调度的影响
              2. 多线程的安全问题
              3. 线程资源宝贵,依赖线程池操作线程,线程池的参数设置问题
              4. 多线程执行是动态的,同时的,难以追踪过程
              5. 多线程的底层是操作系统层面的,源码难度大


4、多线程的基本使用

4.1、定义任务、创建和运行线程

任务: 线程的执行体。也就是我们的核心代码逻辑。
定义线程:

  1. 继承Thread类(可以说是 将任务和线程合并在一起了)
  2. 实现Runnable接口(可以说是 将任务和线程分开了)
  3. 实现Callable接口(利用FutureTask执行任务)
/**
 * 1、corePoolSize 核心线程数大小,当线程数 < corePoolSize ,会创建线程执行 runnable
 *
 * 2、maximumPoolSize 最大线程数, 当线程数 >= corePoolSize的时候,会把 runnable 放入 workQueue中
 *
 * 3、keepAliveTime 保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。
 *
 * 4、unit 时间单位
 *
 * 5、workQueue 保存任务的阻塞队列
 *
 * 6、threadFactory 创建线程的工厂
 *
 * 7、handler 拒绝策略
 */
public class WtyicyThreadPoolExecutor extends ThreadPoolExecutor {

    private ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };


    public WtyicyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public WtyicyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public WtyicyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public WtyicyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    public void execute(Runnable command) {
        int priority = local.get();
        try {
            this.execute(command, priority);
        } finally {
            local.set(0);
        }
    }

    public void execute(Runnable command, int priority) {
        super.execute(new PriorityRunnable(command, priority));
    }

    public <T> Future<T> submit(Callable<T> task, int priority) {
        local.set(priority);
        return super.submit(task);
    }

    public <T> Future<T> submit(Runnable task, T result, int priority) {
        local.set(priority);
        return super.submit(task, result);
    }

    public Future<?> submit(Runnable task, int priority) {
        local.set(priority);
        return super.submit(task);
    }

    protected static class PriorityRunnable<E extends Comparable<? super E>> implements Runnable, Comparable<PriorityRunnable<E>> {
        private final static AtomicLong seq = new AtomicLong();
        private final long seqNum;
        Runnable run;
        private int priority;

        public PriorityRunnable(Runnable run, int priority) {
            seqNum = seq.getAndIncrement();
            this.run = run;
            this.priority = priority;
        }

        public int getPriority() {
            return priority;
        }

        public void setPriority(int priority) {
            this.priority = priority;
        }

        public Runnable getRun() {
            return run;
        }

        @Override
        public void run() {
            this.run.run();
        }

        @Override
        public int compareTo(PriorityRunnable<E> other) {
            int res = 0;
            if (this.priority == other.priority) {
                if (other.run != this.run) {// ASC
                    res = (seqNum < other.seqNum ? -1 : 1);
                }
            } else {// DESC
                res = this.priority > other.priority ? -1 : 1;
            }
            return res;
        }
    }
}
4.1.1、继承Thread类定义,创建,运行线程

Thread实现任务的局限性:

  1. 任务逻辑写在Thread类的run方法中,有单继承的局限性
  2. 创建多线程时,每个任务有成员变量时不共享,必须加static才能做到共享
public class WtyicyThread extends Thread{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " == WtyicyThread执行了");
    }

}

WtyicyThread wtyicyThread = new WtyicyThread();
// 不推荐这种方式
wtyicyThread.start();

WtyicyThreadPoolExecutor wtyicyThreadPoolExecutor = new WtyicyThreadPoolExecutor(5, 6, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3));
wtyicyThreadPoolExecutor.execute(wtyicyThread);

4.1.2、实现Runnable接口定义,创建,运行线程

Runbale相比Callable有以下的局限性:

  1. 任务没有返回值
  2. 任务无法抛异常给调用方
public class WtyicyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " == WtyicyRunnable执行了");
    }
}

WtyicyRunnable wtyicyRunnable = new WtyicyRunnable();
// 不推荐这种方式
new Thread(wtyicyRunnable).start();

wtyicyThreadPoolExecutor.execute(wtyicyRunnable);

4.1.3、实现Callable接口定义,创建,运行线程
public class WtyicyCallable implements Callable {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + " == WtyicyCallable执行了");
        return "SUCCEE";
    }
}

WtyicyCallable wtyicyCallable = new WtyicyCallable();
FutureTask<String> futureTask = new FutureTask<String>(wtyicyCallable);
// 不推荐这种方式
new Thread(futureTask).start();

wtyicyThreadPoolExecutor.execute(futureTask);
System.out.println(futureTask.get());

5、线程状态

       线程的状态可从 操作系统层面分为五种状态 从java api层面分为六种状态。

5.1、五种状态

在这里插入图片描述

  1. 初始状态:创建线程对象时的状态
  2. 可运行状态(就绪状态):调用start()方法后进入就绪状态,也就是准备好被cpu调度执行
  3. 运行状态:线程获取到cpu的时间片,执行run()方法的逻辑
  4. 阻塞状态: 线程被阻塞,放弃cpu的时间片,等待解除阻塞重新回到就绪状态争抢时间片
  5. 终止状态: 线程执行完成或抛出异常后的状态

5.2、六种状态

在这里插入图片描述

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}
  1. NEW 线程对象被创建
  2. Runnable 线程调用了start()方法后进入该状态,该状态包含了三种情况
    2.1. 就绪状态 :等待cpu分配时间片
    2.2. 运行状态:进入Runnable方法执行任务
    2.3. 阻塞状态:BIO 执行阻塞式io流时的状态
  3. Blocked 没获取到锁时的阻塞状态
  4. WAITING 调用wait()、join()等方法后的状态
  5. TIMED_WAITING 调用 sleep(time)、wait(time)、join(time)等方法后的状态
  6. TERMINATED 线程执行完成或抛出异常后的状态

6、线程的相关方法总结

6.1、Thread类中的核心方法

方法名称是否static方法说明
start()让线程启动,进入就绪状态,等待cpu分配时间片
run()重写Runnable接口的方法,线程获取到cpu时间片时执行的具体逻辑
yield()线程的礼让,使得获取到cpu时间片的线程进入就绪状态,重新争抢时间片
sleep(time)线程休眠固定时间,进入阻塞状态,休眠时间完成后重新争抢时间片,休眠可被打断
join()/join(time)调用线程对象的join方法,调用者线程进入阻塞,等待线程对象执行完或者到达指定时间才恢复,重新争抢时间片
isInterrupted()获取线程的打断标记,true:被打断,false:没有被打断。调用后不会修改打断标记
interrupted()打断线程,抛出InterruptedException异常的方法均可被打断,但是打断后不会修改打断标记,正常执行的线程被打断后会修改打断标记
stop()停止线程运行 不推荐
suspend()挂起线程 不推荐
resume()恢复线程运行 不推荐
currentThread()获取当前线程

6.2、Thread类中的核心方法

方法名称方法说明
wait()/wait(long timeout)获取到锁的线程进入阻塞状态
notify()随机唤醒被wait()的一个线程
notifyAll()唤醒被wait()的所有线程,重新争抢时间片

7、六种线程状态和方法的对应关系

在这里插入图片描述

7.1、线程的礼让-yield()&线程的优先级

       t2线程每次执行时进行了yield(),线程1执行的机会明显比线程2要多。

Runnable r1 = () -> {
    int count = 0;
    for (;;){
       log.info("---- 1>" + count++);
    }
};
Runnable r2 = () -> {
    int count = 0;
    for (;;){
    	// 设置礼让
        Thread.yield();
        log.info("            ---- 2>" + count++);
    }
};
wtyicyThreadPoolExecutor.execute(r1);
wtyicyThreadPoolExecutor.execute(r2);

wtyicyThreadPoolExecutor.execute(r1 , 1 );
wtyicyThreadPoolExecutor.execute(r2,10);
//            Thread t1 = new Thread(r1,"t1");
//            Thread t2 = new Thread(r2,"t2");
//			  cpu比较忙时,优先级高的线程获取更多的时间片
//			  cpu比较闲时,优先级设置基本没用
//            t1.setPriority(Thread.MIN_PRIORITY);
//            t2.setPriority(Thread.MAX_PRIORITY);
//            t1.start();
//            t2.start();

7.2、守护线程

       默认情况下,java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫守护线程,当所有的非守护线程都结束后,即使它没有执行完,也会强制结束。
       默认的线程都是非守护线程,垃圾回收线程就是典型的守护线程

// 设为true表示未守护线程,当主线程结束后,守护线程也结束。
// 默认是false,当主线程结束后,thread继续运行,程序不停止
t1.setDaemon(true);

7.3、线程的阻塞

       线程的阻塞可以分为好多种,从操作系统层面和java层面阻塞的定义可能不同,但是广义上使得线程阻塞的方式有下面几种:

  1. BIO阻塞,即使用了阻塞式的io流
  2. sleep(long time) 让线程休眠进入阻塞状态
  3. a.join() 调用该方法的线程进入阻塞,等待a线程执行完恢复运行
  4. sychronized或ReentrantLock 造成线程未获得锁进入阻塞状态
  5. 获得锁之后调用wait()方法 也会让线程进入阻塞状态
  6. LockSupport.park() 让线程进入阻塞状态
7.3.1、sleep()

       使线程休眠,会将运行中的线程进入阻塞状态。当休眠时间结束后,重新争抢cpu的时间片继续运行

try {
   // 休眠2秒
   // 该方法会抛出 InterruptedException异常 即休眠过程中可被中断,被中断后抛出异常
   Thread.sleep(2000);
 } catch (InterruptedException异常 e) {
 }
 try {
   // 使用TimeUnit的api可替代 Thread.sleep 
   TimeUnit.SECONDS.sleep(1);
 } catch (InterruptedException e) {
 }
7.3.2、join()

       join是指调用该方法的线程进入阻塞状态,等待某线程执行完成后恢复运行

Thread t = new Thread(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    r = 10;
});

t.start();
// 让主线程阻塞 等待t线程执行完才继续执行 
// 去除该行,执行结果为0,加上该行 执行结果为10
t.join();

7.3.2、线程的打断-interrupt()

       打断标记:线程是否被打断,true表示被打断了,false表示没有
       isInterrupted() 获取线程的打断标记 ,调用后不会修改线程的打断标记
       interrupt()方法用于中断线程
       可以打断sleep,wait,join等显式的抛出InterruptedException方法的线程,但是打断后,线程的打断标记还是false
       打断正常线程 ,线程不会真正被中断,但是线程的打断标记为true
       interrupted() 获取线程的打断标记,调用后清空打断标记 即如果获取为true 调用后打断标记为false (不常用)
       interrupt实例: 有个后台监控线程不停的监控,当外界打断它时,就结束运行

@Slf4j
class TwoPhaseTerminal{
    // 监控线程
    private Thread monitor;

    public void start(){
        monitor = new Thread(() ->{
           // 不停的监控
            while (true){
                Thread thread = Thread.currentThread();
                 // 判断当前线程是否被打断
                if (thread.isInterrupted()){
                    log.info("当前线程被打断,结束运行");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    // 监控逻辑中被打断后,打断标记为true
                    log.info("监控");
                } catch (InterruptedException e) {
                    // 睡眠时被打断时抛出异常 在该处捕获到 此时打断标记还是false
                    // 在调用一次中断 使得中断标记为true
                    thread.interrupt();
                }
            }
        });
        monitor.start();
    }

    public void stop(){
        monitor.interrupt();
    }
}


8、往期佳文

8.1、面试系列

1、吊打面试官之一面自我介绍
2、吊打面试官之一面项目介绍
3、吊打面试官之一面系统架构设计
4、吊打面试官之一面你负责哪一块
5、吊打面试官之一面试官提问

······持续更新中······


8.2、技术系列

1、吊打面试官之分布式会话
2、吊打面试官之分布式锁
3、吊打面试官之乐观锁
4、吊打面试官之幂等性问题
5、吊打面试关之分布式事务
6、吊打面试官之项目线上问题排查

······持续更新中······

8.3、源码系列

1、源码分析之SpringBoot启动流程原理
2、源码分析之SpringBoot自动装配原理
3、源码分析之ArrayList容器
4、源码分析之LinkedList容器
5、源码分析之HashMap容器

8.4、数据结构和算法系列

1、数据结构之八大数据结构
2、数据结构之动态查找树(二叉查找树,平衡二叉树,红黑树)

······持续更新中······


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值