欢迎光临散文网 会员登陆 & 注册

一份珍贵的多线程学习笔记

2020-12-17 14:08 作者:编程大战  | 我要投稿

多线程理解学习:

多线程

百度百科:多线程(multithreading),是指从软件或硬件上实现多个线程并发执行的请求具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫做“线程”(Thread),利用它编程的概念就叫做“多线程处理”。

进程与线程区别:
进程:一个程序就是一个进程例如我正在运行的微信,网易云等都属于一个进程。

线程:线程是运行在计算机操作系统上运算调度最小的单位,它包含在我们的进程中,在统一进程中线程拥有该进程的全部系统资源。

并行与并发:

举个例子:

例1:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 (不一定是同时的)
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

例2:
你在打游戏,女朋友突然打来了视频电话,如果你无视女朋友的电话,一直到游戏结束才回她电话,这就说明你不支持并发,也不支持并行。
你在打游戏中,女票来了电话,你赶紧接了电话,放下了手头的游戏,等你女票电话结束你才继续玩你的游戏,这说明你支持并发。(不一定是同时的)
你玩游戏玩到一半,女票来了电话,你一边接电话一边打你的游戏说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时;

并行的关键是你有同时处理多个任务的能力。

所以我认为它们最关键的点就是:是否是『同时』。并发是轮流处理多个任务,并行是同时处理多个任务

线程池:

线程的创建方式:Java就是一个天生的多线程语言。在我们运行main方法的时候,其实就是创建并启动了一个main线程。

线程的状态(五种):

  • 1、新建:新创建一个线程。

  • 2、就绪:线程对象创建后,其他线程(比如main线程)调用了该对象的start( )方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权。

  • 3、运行:获取了CPU的使用权。

  • 4、阻塞:线程可能被挂起,或者被中断,让出CPU的使用权。

  • 5、死亡:线程run()、main()方法执行结束,或者因异常退出了run()方法。则该线程结束生命周期。死亡的线程不可再次复生。

在Java中创建线程有三种方式:

1、通过集成Thread类,重写run()方法创建线程

输出结果:

2、通过实现Runnable接口创建线程

输出结果:

3、通过实现Callable接口和Future创建线程,该方式需要通过FutureTask帮助获取返回值。

输出结果:

为什么需要线程池?

线程的创建和销毁都会消耗系统的资源,将线程放在一个缓存池,需要使用时直接从缓存池中获取线程,通过重用使用已创建的线程来降低系统所消耗的能源。

线程池的使用:

在Executor类中有4种线程池的创建方法。

1、newCachedThreadPool():创建一个缓存线程池,创建线程数量不限制,线程长时间未使用会被回收,如果有大量线程同时运行可能会导致系统瘫痪。SynchronousQueue(同步队列):内部只能包含一个元素的队列。

2、newFixedThreadPool():创建固定线程的线程池,LinkedBlockingQueue队列中大小是不限的,所有可能会出现内存溢出的情况。

3、newSIngleTreadExecutor()创建一个只有一个线程的线程池,俗称单例线程池同样也是采用的LinkedBlockingQueue.

4、newScheduledThreadPool():创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

线程池使用注意事项:

阿里开发规范(原文):

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2) CachedThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

ThreadPoolExecutor:

参数说明:

corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:线程保持活跃时间
unit:线程活跃时间的单位
workQueue:任务队列
threadFactory;线程工厂,线程创建的方式
handler:拒绝策略

TimeUnit类,配合keepAliveTime使用指定时间格式

纳秒 NANOSECONDS
微秒 MICROSECONDS
毫秒 MILLISECONDS
秒 SECONDS
分 MINUTES
时 HOURS
天 DAYS

拒绝策略

RejectedExecutionHandler:ThreadPoolExecutor提供了四种拒绝策略

1、AbortPolicy(默认使用):抛出异常

2、CallerRunsPolicy:在当前线程运行该任务

3、DiscardPolicy:丢弃任务

4、DiscardOldestPolicy:丢弃最早的任务

线程池的实现原理:

当有新任务时,会创建线程来执行任务,当线程数达到corePoolSize时,就会将任务放在阻塞队列,当阻塞队列满了,并且线程数达到了maximumPoolSize时,会触发拒绝策略拒绝任务。

锁:

什么时候需要用到锁:

多线程的环境下肯定会出现线程安全的问题,通过锁可以解决线程的安全问题,保证数据的一致性。

锁升级:

锁状态:偏向锁,轻量级锁,重量级锁(级别由低到高)

1. 偏向锁:

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

2. 轻量级锁

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced MarkWord。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

3. 轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。

volatile:
Java中的关键字,基于jvm实现。可以保证被他修饰的方法或是代码块在任意一个时刻只能有一个线程执行。

synchronized实现原理

synchronized使用方式

Java中所有对象都可以被当做synchronized的锁。

1、synchronized使用在普通方法中,锁是当前对象,进入被synchronized修饰的普通方法时要获取当前对象的锁。

输出结果:

2、synchronized使用在静态方法中,锁是当前class对象,进入被synchronized修饰的静态方法时要获取当前的class对象锁。

输出结果:

3、synchronized使用在代码块中,锁是代码块指定对象的锁,进入被synchronized修饰的代码块时需要获取到括号中对象的锁。

输出结果:

Lock锁

在Lock接口出现之前,只能靠synchronized关键字实现锁功能的,在JDK1.5后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,并且让我们可以比synchronized为灵活的运行性锁。自己管理锁则需要手动获取锁和释放锁,使用不当就会造成系统瘫痪,比如死锁。

CAS(Compare and Swap)

Compare and Swap:比较并替换,CAS是区别于synchronize的一种乐观锁。CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。在sun.misc包下有个被final关键字修饰的Unsafe的类,该类不对外提供。该类中的方法都被native关键字修饰,表示该方法由本地实现,Unsafe底层均是采用的C++语言实现的,保证在cpu上运行的原子性。Unsafe类只提供了三种原子操作方法,其他类型都是转成这三种类型再使用对应的方法去原子更新的。

AQS(AbstractQueuedSynchronizer)

AQS全称:AbstractQueuedSynchronizer是一个同步队列(它是一个抽象类),AQS底层使用了模板方法模式实现了对同步状态的管理,对阻塞线程进行排队,等待通知等等。AQS的核心也包括了这些方面:同步队列,独占锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现。AbstractQueuedSynchronizer是一个FIFO(First Input First Output)即先进先出的队列。

独占锁

独占释放锁

公平锁

ReentrantLock相关操作

公平锁与非公平锁字面上意思就是一个是公平的竞争,一个是非公平的竞争。
ReentrantLock默认使用的是非公平锁,因为非公平锁可以减少线程间的切换,可以避免资源的浪费,也可能会导致线程一直处于阻塞状态。Doug Lea大神选择了非公平锁这个也很符合人类的生活,这个世界是不公平的,因为有了种种的不公平,我们的生活才可以如此的绚丽多彩,想象一下,如果这个世界是公平的,人人平等,那该会有多枯燥。


本文到这里就结束了~感谢小伙伴的阅读哦~

什么?没看够?想要获取更多知识点?评论回复“666”,尚学堂最新的教学资料就是你的啦~



一份珍贵的多线程学习笔记的评论 (共 条)

分享到微博请遵守国家法律