导图社区 多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
编辑于2022-09-26 20:38:06 四川省listener 音标['lisnә] 读音 汉语翻译 n. 收听者, 听众 英语解释: 名词listener: someone who listens attentively 同义词:hearer, auditor, attender
Filter过滤器(重要) Javaweb中的过滤器可以拦截所有访问web资源的请求或响应操作。 1、Filter快速入门 1.1、步骤: 1. 创建一个类实现Filter接口 2. 重写接口中方法 d...
会话的解释 [conversation] 指两人以上的对话(多用于学习别种语言或方言时) 详细解释 (1).聚谈;对话。现多用于学习别种语言或方言时
社区模板帮助中心,点此进入>>
listener 音标['lisnә] 读音 汉语翻译 n. 收听者, 听众 英语解释: 名词listener: someone who listens attentively 同义词:hearer, auditor, attender
Filter过滤器(重要) Javaweb中的过滤器可以拦截所有访问web资源的请求或响应操作。 1、Filter快速入门 1.1、步骤: 1. 创建一个类实现Filter接口 2. 重写接口中方法 d...
会话的解释 [conversation] 指两人以上的对话(多用于学习别种语言或方言时) 详细解释 (1).聚谈;对话。现多用于学习别种语言或方言时
多线程
介绍
多线程是指从软件或者硬件上实现多个线程并发执行的技术。
具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能
并行与并发
并行
在同一时刻,有多个指令在多个CPU上同时执行
并发
在同一时刻,有多个指令在单个CPU上交替执行
进程与线程
进程
进程:是正在运行的软件
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
线程
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
线程调度
两种调度方式
分时调度模型
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
实现方式
1.继承Thread类
方法
void run()
在线程开启后,此方法将被调用执行
void start()
使此线程开始执行,Java虚拟机会调用run方法()
区别
run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程
start():启动线程;然后由JVM调用此线程的run()方法
步骤
1.定义一个类MyThread继承Thread类
2.在MyThread类中重写run()方法
线程开启之后执行的代码
3.创建MyThread类的对象
4.启动线程start()
注意
因为run()是用来封装被线程执行的代码,所以必须重写run()方法
2.实现Runnable接口
构造方法
Thread(Runnable target)
分配一个新的Thread对象
Thread(Runnable target, String name)
分配一个新的Thread对象
步骤
1.定义一个类MyRunnable实现Runnable接口
2.在MyRunnable类中重写run()方法
3.创建MyRunnable类的对象
4.创建Thread类的对象,把MyRunnable对象作为构造方法的参数
5.启动线程
3.利用Callable、Future接口
方法
V call()
计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable callable)
创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()
如有必要,等待计算完成,然后获取其结果
步骤
定义一个类MyCallable实现Callable接口
在MyCallable类中重写call()方法
创建MyCallable类的对象
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
创建Thread类的对象,把FutureTask对象作为构造方法的参数
启动线程
再调用get方法,就可以获取线程结束之后的结果
方式对比
实现Runnable、Callable接口
好处: 扩展性强,实现该接口的同时还可以继承其他的类
缺点: 编程相对复杂,不能直接使用Thread类中的方法
继承Thread类
好处: 编程比较简单,可以直接使用Thread类中的方法
缺点: 可以扩展性较差,不能再继承其他的类
常见方法
void setName(String name)
将此线程的名称更改为等于参数name
线程是有默认名字的,格式:Thread-编号
通过构造方法也可以设置线程名称
String getName()
返回此线程的名称
public static Thread currentThread()
返回对当前正在执行的线程对象的引用
调用方式
Thread.currentThread()
线程休眠
static void sleep(long millis)
使当前正在执行的线程停留(暂停执行)指定的毫秒数
线程调度
final int getPriority()
返回此线程的优先级
final void setPriority(int newPriority)
更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
守护线程
void setDaemon(boolean on)
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了
线程同步
数据安全问题
产生原因
线程执行的随机性导致的,可能在执行过程中丢失cpu的执行权,导致出现问题
出现的条件
是多线程环境
有共享数据
有多条语句操作共享数据
如何解决
基本思想
让程序没有安全问题的环境
具体实现
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
Java提供了同步代码块的方式来解决
同步代码块
介绍
作用
锁多条语句操作共享数据
好处
解决了多线程的数据安全问题
弊端
线程很多时,每个线程都会去判断同步上的锁,很耗费资源,无形中会降低程序的运行效率
格式
注意
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁,且锁对象需要是唯一的
对可能有安全问题的代码加锁,多个线程必须使用同一把锁
默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭,当线程执行完出来了,锁才会自动打开
同步方法
介绍
同步方法:就是把synchronized关键字加到方法上
同步方法的锁对象是this
格式
同步代码块和同步方法的区别
同步代码块可以锁住指定代码同步方法是锁住方法中所有代码
同步代码块可以指定锁对象同步方法不能指定锁对象
静态同步方法
介绍
静态同步方法:就是把synchronized关键字加到静态方法上
同步静态方法的锁对象是类名.class
格式
Lock锁
介绍
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,可以采用它的实现类ReentrantLock来实例化
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
构造方法
ReentrantLock()
创建一个ReentrantLock的实例
加锁解锁方法
void lock()
获得锁
void unlock()
释放锁
死锁
介绍
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
产生死锁的情况
资源有限
同步嵌套
生产者消费者
介绍
生产者消费者模式是一个十分经典的多线程协作的模式
所谓生产者消费者问题,实际上主要是包含了两类线程:一类是生产者线程用于生产数据,一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
Object类的等待和唤醒方法
void wait()
导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()
唤醒正在等待对象监视器的单个线程
void notifyAll()
唤醒正在等待对象监视器的所有线程
阻塞队列
介绍
继承结构
ArrayBlockingQueue
底层是数组,有界
LinkedBlockingQueue
底层是链表,无界.但不是真正的无界,最大为int的最大值
BlockingQueue的核心方法
put(anObject)
将参数放入队列,如果放不进去会阻塞
take()
取出第一个数据,取不到会阻塞
线程池
介绍
采用线程池可以提高性能。程池在启动时,会创建大量空闲线程,当向线程池提交任务时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行
设计思路
1. 准备一个任务容器
2. 一次性启动多个(2个)消费者线程
3. 刚开始任务容器是空的,所以线程都在wait
4. 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
5. 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来
线程状态
线程对象在不同的时期有不同的状态,Java中的线程状态被定义在了java.lang.Thread.State枚举类中
分类
新建NEW
一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动,还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征
就绪RUNNABLE
当调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态,可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的调度
阻塞BLOCKED
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
等待WAITING
一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作
计时TIMED_WAITING
一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)
结束TERMINATED
一个完全运行完成的线程的状态。也称之为终止状态、结束状态
转换图
创建线程池
Executors默认线程池
static ExecutorService newCachedThreadPool()
创建一个默认的线程池
池子中默认是空的.默认最多可以容纳int类型的最大值
ExecutorService
可以帮助控制线程池
static newFixedThreadPool(int nThreads)
创建一个指定最多线程数量的线程池
参数不是初始值而是最大值
ThreadPoolExecutor
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
参数详解
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null,当提交的任务大于池中最大线程数量加上队列容量的和时拒绝任务
任务拒绝策略
ThreadPoolExecutor.AbortPolicy
丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy
丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy
抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy
调用任务的run()方法绕过线程池直接执行
原子性
介绍
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体
问题及解决
问题
当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题
解决
volatile
介绍
Volatile : 强制线程每次在使用的时候,都会看一下共享区域最新的值
volatile关键字只能保证线程每次在使用共享数据的时候是最新值,不能保证原子性
synchronized
1 ,线程获得锁
2 ,清空变量副本
3 ,拷贝共享变量最新的值到变量副本中
4 ,执行代码
5 ,将修改后变量副本中的值赋值给共享数据
6 ,释放锁
原子包atomic
介绍
java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式
因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)
AtomicInteger原子更新整型
构造方法
public AtomicInteger()
初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue)
初始化一个指定值的原子型Integer
常用方法
int get()
获取值
int getAndIncrement()
以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet()
以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data)
以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value)
以原子方式设置为newValue的值,并返回旧值。
原理
自旋锁
CAS 算法
有3个操作数(内存值V, 旧的预期值A,要修改的值B)
当旧的预期值A == 内存值 此时修改成功,将V改为B
当旧的预期值A!=内存值 此时修改失败,不做任何操作
并重新获取现在的最新值(这个重新获取的动作就是自旋)
synchronized和CAS的区别
相同点
在多线程情况下,都可以保证共享数据的安全性
不同点
synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。如果别人修改过,那么我再次获取现在最新的值。如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)
并发工具类
Hashtable
介绍
在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下
Hashtable采取悲观锁synchronized的形式保证数据的安全性只要有线程访问,会将整张表全部锁起来,所以Hashtable的效率低下
ConcurrentHashMap
介绍
在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap
体系结构
原理
JDK 7
JDK 8
1,如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表
2,计算当前元素应存入的索引。
3,如果该索引位置为null,则利用cas算法,将本结点添加到数组中。
4,如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。
5,当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性
CountDownLatch
介绍
使用场景
让某一条线程等待其他线程执行完毕之后再执行
常用方法
public CountDownLatch(int count)
参数传递线程数,表示等待线程数量
参数写等待线程的数量。并定义了一个计数器。
public void await()
让线程等待,当计数器为0时,会唤醒等待的线程
public void countDown()
当前线程执行完毕
线程执行完毕时调用,会将计数器-1
Semaphore
介绍
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
使用场景
可以控制访问特定资源的线程数量
主题