Java - 多线程基础

it2023-06-07  72

文章目录

线程与进程线程调度同步与异步&并发与并行 继承Thread (基本使用)实现RunnableThread类设置和获取线程名称线程休眠sleep线程阻塞 (耗时操作)线程的中断守护线程 线程安全问题解决办法1. 同步代码块(隐式锁 => 自身加锁自身解锁)2. 同步方法(隐式锁)3. 显式锁Lock 线程死锁多线程通信问题生产者与消费者线程的六种状态 第三种创建线程的方式: 带返回值的线程Callable常用方法Runnable 与 Callable的相同点Runnable 与 Callable的不同点 线程池概述1. 缓存线程池2. 定长线程池3. 单线程线程池4. 周期定长线程池 Lambda表达式

线程与进程

进程 => 一个内存中的应用程序

每个进程都有一个独立的内存空间

线程 => 进程中的一个执行路径

进程包含线程, 至少一个线程共享内存空间, 自由切换, 并发执行

乐说 =>

电脑运行着QQ, 微信, idea, vscode等10个应用程序(进程), 共有1600多个线程, CPU都没有怎么动诶~ R7牛啤, AMD, YES!!

线程调度

分时调度 => 所有线程轮流使用CPU(10秒, 5秒做A , 5秒做B)抢占式调度 => 优先级高的先执行 CPU, 抢占式调度 => 多线程不能提高程序运行速度(总时间就那么多, 还得切换线程), 但能提高运行效率 (执行的时候顺便给他安排别的事)

eg =>

4个面试官找1000个人面试快, 还是1000个人去找面试官快(不用等待, 面试一个就下一个)

同步与异步&并发与并行

同步:

排队执行, 效率低但是安全

异步:

同时执行, 效率高但是数据不安全

并发:

同一时间段发生

并行:

同一时刻发生(相当于同时发生)

继承Thread (基本使用)

//并发执行 MyThread m = new MyThread(); m.start(); //1. main方法执行后, 创建a对象的同时, 线程也会开启 //2. java是抢占式调度, 交替并发执行 //3. 每一个线程都有一个栈空间, 共用一份堆内存 (先入栈再弹栈操作)

实现Runnable

相比继承Thread有如下优势:

通过创建任务,然后给线程分配的方式来实现的多线程, 更适合多个线程同时执行相同任务的情况.可以避免单继承所带来的局限性.任务与线程本身是分离的,提高了程序的健壮性.后续学习的线程池技术,接受Runnable类型的任务,不接收Thread 类型的线程. //1 创建一个任务对象 MyRunnable r = new MyRunnable(); //创建线程并给他任务 (任务线程分离提高健壮性) Thread t = new Thread(r); //启动线程 t.start();

Thread类

常用API:

start()

sleep(long millis): 休眠

setDaemon(boolean): 守护线程 (人在塔在) (会自动释放)

设置和获取线程名称

//如何获取线程的名称 System.out.println(Thread.currentThread().getName()); //两种设置线程名称的方式 Thread t = new Thread(new MyRunnable()); t.setName("wwww"); t.start(); new Thread(new MyRunnable(),"锄禾日当午").start(); //不设置的有默认的名字 new Thread(new MyRunnable()).start();

线程休眠sleep

Thread的静态方法sleep可直接通过类调用

Thread.sleep(1000);

线程阻塞 (耗时操作)

阻塞 => (广义)消耗时间的操作

线程的中断

线程是一个独立的执行路径, 中断应由其自己决定 (所以stop()强制关闭的方法时不安全的, 故而将其删除了)

所以如何进行中断操作呢 ? => interrupt()方法 main() => thread.interrupt(); //main函数调用interrupt(), 任务捕获线程中断异常, catch块中进行return; 结束任务run(); try { Thread.sleep(1000); } catch (InterruptedException e) { //e.printStackTrace(); System.out.println("发现了中断标记,线程自杀"); return; }

守护线程

会随着主程序运行的结束而结束

//线程分为守护线程和用户线程 //用户线程:当一个进程不包含任何的存活的用户线程时,进行结束 //守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。 Thread t1 = new Thread(new MyRunnable()); //设置守护线程 t1.setDaemon(true); t1.start();

线程安全问题

多线程抢占式调度可能会出现执行到一半, 另一个线程插手也开始执行的问题, 到临界判断条件时就已发生错误

synchronize => 同步 (锁)

解决办法

1. 同步代码块(隐式锁 => 自身加锁自身解锁)

//o就是一个加锁的标识, o就是锁 private Object o = new Object(); synchronized (o) { //加锁 => //可以理解为很多人无秩序的站在这, 加锁就相当于加了个保安, 让这个没有秩序的队伍变得很有 ... }

代码示例:

public static void main(String[] args) { Runnable run = new Ticket(); new Thread(run,"A").start(); new Thread(run,"B").start(); new Thread(run,"C").start(); } static class Ticket implements Runnable{ private int count = 20; //相当于锁的一个标识 //锁必须声明在外部, 确定是同一把锁!!同一把!!就是同一个任务!! private Object o = new Object(); @Override public void run() { while (true) { //锁会加在每一次卖票的前面, 所以其他线程有几率会抢到这个锁, 但一开始抢到锁的线程有优势, 再一次进行循环时抢到锁的几率也较大 synchronized (o){ if(count>0){ System.out.println("准备卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName() + "卖票结束,余票:" + count); }else { break; } } } } }

2. 同步方法(隐式锁)

//将java代码 //public => 用this代表同一任务的锁 //public static => 用Ticket.class代表同一任务的锁

代码示例:

static class Ticket implements Runnable{ //总票数 private int count = 10; @Override public void run() { synchronized (this){ ... //sale是用同步方法加锁, 这是同步代码块, 但是都是对同一任务进行加锁的 } while (true) { boolean flag = sale(); if(!flag){ break; } } } public synchronized boolean sale(){ ...//加锁方式二 => 同步方法 }

3. 显式锁Lock

//更加体现了面向对象的思想 //使用方法 //新建Lock类型对象 private Lock l = new TeentrantLock(); //重写Rrunnable的run方法里(任务) ... l.lock(); ... l.unlock(); ... //在lock和unlock的代码块内是线程安全的

注意:

公平锁 => new ReentrantLock(true); => 一百个人, 先来先到, A先来就A先执行

非公平锁 => new ReentrantLock(falese); => 一百个人, 谁抢到这个锁, 谁就先执行

要求 =>

lock() 需要紧跟try代码块

且unlock() 需要在finally代码块的第一行

线程死锁

互相怀疑 => 我预测了你预测我的预测

可见 => 给女朋友讲编程(3) - 线程死锁

多线程通信问题

各线程通过合作完成同一项任务 => 男女搭配干活不累

wait() 使当前线程等待直到他该醒来的时候

notifyAll() 唤醒在当前方法内睡着的所有线程

生产者与消费者

经典定义:

生产者生产完资料之后, 进入休眠状态, 消费者对资料进行消费, 消费后生产者继续生产资料

多线程通信其实也是排队的原理, 那为什么不能使用synchronized()同步呢 ?

synchronized加的锁不是公平锁, 所以就会发生消费者在生产者还未生产完资料时就将资料给消费掉 或者 出现生产者不断的抢占锁生产资料而消费者永远抢不到锁的情况 或者 消费者一直对一个资料进行消费, 永远不会结束消费

生产者和消费者所执行的任务不是同一任务, 所以synchronized关键字没办法对其进行修饰就会产生线程不安全的问题, 因此 =>

解决办法=> 休眠和唤醒 + flag标志 + synchronized标识

Class A{ boolean flag = true; public synchronized void b(){ if(flag){ ... flag = false; this.notifyAll();//唤醒所有线程, 包括生产者消费者(b, c) this.wait();//让自己(b)休眠 } } public synchronized void c(){ if(!flag){ ... flag = true; this.notifyAll();//唤醒所有线程(b, c) this.wait();//c休眠 } } }

线程的六种状态

第三种创建线程的方式: 带返回值的线程Callable

1. 编写类实现Callable接口 , 实现call方法, call方法会返回计算结果 <T> class XXX implements Callable<T> { @Override public <T> call() throws Exception { return T; } } 2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象 FutureTask<Integer> future = new FutureTask<>(XXX); 3. 通过Thread,启动线程 new Thread(future).start();

常用方法

Vget() 如有必要,等待计算完成,然后获取其结果。Vget(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。booleanisCancelled() 如果在任务正常完成前将其取消,则返回 true。booleanisDone() 如果任务已完成,则返回 true。

Runnable 与 Callable的相同点

都是接口都可以编写多线程程序都采用Thread.start()启动线程

Runnable 与 Callable的不同点

Runnable没有返回值;Callable可以返回执行结果 Callable接口的call()允许抛出异常;Runnable的run()不能抛出;

线程池概述

历史:

池 => 容器, 线程通常用来做一个小任务之后就会释放掉

线程的工作流程:

创建线程 => 创建任务 => 执行任务 => 关闭任务

频繁创建线程和销毁线程需要时间, 大大降低系统的效率

因此出现 => 线程池

线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

合理设计线程池的长度, 否则也会令资源有多余的开销

1. 缓存线程池

//缓存线程池特点 => 无限制长度 //任务加入后的执行流程 //1判断线程池是否存在空闲线程 2存在则使用 3不存在则创建线程并使用 //向线程池中加入新的任务 ExecutorService service = Executors.newCachedThreadPool(); //指挥线程池执行新的任务 service.execute(new Runnable());

2. 定长线程池

/*定长线程池 长度是指定的线程池 加入任务后的执行流程 1 判断线程池是否存在空闲线程 2 存在则使用 3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用 4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程 **/ ExecutorService service = Executors.newFixedThreadPool(2); service.execute(new Runnable());

3. 单线程线程池

/*单线程线程池 => 只有一个线程 执行流程 1 判断线程池的那个线程是否空闲 2 空闲则使用 3 不空闲则等待它空闲后再使用 **/ ExecutorService service = Executors.newSingleThreadExecutor(); service.execute();

4. 周期定长线程池

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); //定时执行一次 //参数1:定时执行的任务 //参数2:时长数字 //参数3:2的时间单位 Timeunit的常量指定 scheduledExecutorService.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"周期定长线程池"); } },5, TimeUnit.SECONDS); /* 周期性执行任务 参数1:任务 参数2:延迟时长数字(第一次在执行上面时间以后) 参数3:周期时长数字(每隔多久执行一次) 参数4:时长数字的单位 * **/ scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"周期定长线程池"); } },5,1,TimeUnit.SECONDS);

Lambda表达式

将匿名内部类简化, 利用的是面向函数编程的思想

Thread t = new Thread(() -> System.out.println("Lambda表达式")); t.start();
最新回复(0)