高伸缩性的并发编程是一种艺术,是成为高级程序员的必备知识点之一,最近总结了一下相关方面的知识。
借鉴过得博客有的我也不知道原文作业是谁
https://blog.csdn.net/qq_34337272/article/details/81072874
https://www.cnblogs.com/dolphin0520/p/3932921.html
一、线程
什么是线程?
线程是进程中单一顺序的控制流。
什么是进程?
进程是程序运行的过程。
1、进程与线程的关系:
1)线程是进程的最小执行单元;
2)一个进程中至少存在一个线程;
3)线程决定进程的生命周期 —— 一个线程启动,进程就开始执行;所有线程执行结束,这个进程执行结束;
4)多个线程共享一个进程的内存空间、一组系统资源。
2、进程与线程的区别:
1)每个进程都有自己的独立的内存空间、一组系统资源,而线程共享所属进程的内存空间、一组系统资源;
进程是独立的,同一个进程的线程是有联系的。
2)进程之间通信开销较大,线程之间通信开销较小。
3、多线程
一个进程中可以同时存在多个线程;
同一个进程中的多个线程之间可以并发执行;
多线程的执行方式:抢占式;
抢占CPU资源,计算机由CPU执行程序,只有拥有CPU资源的程序,才会被执行。
CUP资源:CUP的控制权,这个控制权具有时间性,时间长度是随机的;这段时间非常短,所以将其称为时间片。
时间片:线程抢占到一段"CPU控制权"的时间。
4、多线程内存如何分配
栈 独立
堆 共享
方法区 共享
程序计数器 独立
记录程序执行到位置。
5、自定义线程的两种方式:
1)继承Thread类,重写run()方法
创建线程对象:
new 子类的构造方法(参数列表);
2)实现Runnable接口,重写run()方法
创建线程对象:
new Thread(new 实现类构造方法(参数列表));
注意:Runnable接口只有一个run()方法。
6、Thread类常用方法:
void start()
//使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
static void yield()
//暂停当前正在执行的线程对象,并执行其他线程。
static void sleep(long millis)
//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
void interrupt()
//中断线程。
int getPriority()
//返回线程的优先级。
void setPriority(int newPriority)
//更改线程的优先级。 优先级的范围:1…10 1最小,10最大,默认5。
boolean isAlive()
//测试线程是否处于活动状态。 活的状态:就绪、运行、阻塞
static int activeCount()
//返回当前线程的线程组中活动线程的数目。
void join()
//等待该线程终止。
7、**线程五大状态**
1)新建状态
使用new关键字创建线程对象,此时就是新建状态。
2)就绪状态
线程对象调用start方法,进入就绪状态,处于就绪状态的线程并不一定马上就会执行 run 方法,还需要等待CPU的调度。
3)运行状态
线程获得CUP资源,执行run()方法,进入运行状态。
4)阻塞状态
调用sleep()方法、等待I/O资源、、等待同步锁、调用wait()等待被唤醒等,进入阻塞状态。
注意:进入阻塞状态的线程,会立刻让出CPU资源(控制权)。
5)结束状态
run()执行完毕,进入结束状态; 正常结束。
调用stop()、destroy()方法,进入结束状态; 异常结束。
使用interrupt()方法设置中断标识,当线程发生阻塞时,进入结束状态; 异常结束。
8、线程的协作
1)join(); 当前线程等待另一个线程执行结束。
9、线程的结束
正常结束:run方法执行完毕。
异常结束:使用stop()、destroy()方法强制结束线程,已过时,不推荐使用。
使用interrupt()方法中断线程,来实现强制结束线程。
注意:interrupt()方法的本意并不是强制结束线程,而是设置中断标识,设置了中断标识的线程,在发生阻塞时,
抛出异常,进入就绪状态。
要实现强制结束线程,run方法中的实现(代码)全写在try{}中,若抛出异常,try{}中的剩余代码不再执行,
异常处理后,run()方法就执行结束;线程进入结束状态。
二、高并发
什么叫高并发?
高并发(High Concurrency)是一种系统运行过程中遇到的一种“短时间内遇到大量操作请求”的情况,主要发生在web系统集中大量访问收到大量请求(例如:12306的抢票情况;天猫双十一活动)。该情况的发生会导致系统在这段时间内执行大量操作,例如对资源的请求、数据库的操作等。
高并发相关常用的一些指标有:响应时间、吞吐量、每秒查询率QPS、并发用户数
1、响应时间(Response Time)
响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间
2、吞吐量(Throughput)
吞吐量:单位时间内处理的请求数量。
3、每秒查询率QPS(Query Per Second)
QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
4、并发用户数
并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。
三、高并发和多线程的关系和区别
“高并发和多线程”总是被一起提起,给人感觉两者好像相等,实则 高并发 ≠ 多线程
多线程是Java的特性,因为现在cpu都是多核多线程的,可以同时执行几个任务,为了提高jvm的执行效率,Java提供了这种多线程的机制,以增强数据处理效率。多线程对应的是cpu,高并发对应的是访问请求
总之:多线程即可以这么理解:多线程是处理高并发的一种编程方法,即并发需要用多线程实现。
并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题
1、原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2、可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
3、有序性:即程序执行的顺序按照代码的先后顺序执行,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
所以:要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行
懒加载(lazy loading)—即为延迟加载,顾名思义在需要的时候才加载,这样做效率会比较低,但是占用内存低,iOS设备内存资源有限,如果程序启动使用一次性加载的方式可能会耗尽内存,这时可以使用懒加载,先判断是否有,没有再去创建
懒加载的好处:不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强代码之间的独立性强,低耦合,节省了内存资源
线程安全就是同步,
线程不安全就是异步
下面是关于锁的总结很详细
乐观锁和悲观锁:
乐观锁和悲观锁都是一种思想,并不是真实存在于数据库中的一种机制。
悲观锁:
当认为数据被并发修改的几率比较大,需要在修改之前借助于数据库锁机制,先对数据进行加锁的思想被称为悲观锁,又称PCC(Pessimistic Concurrency Control)。在效率方面,处理锁的操作会产生了额外的开销,而且增加了死锁的机会。当一个线程在处理某行数据的时候,其它线程只能等待。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁:
乐观锁思想认为,数据一般是不会造成冲突的。只有在提交数据的时候,才会对数据的冲突进行检测。当发现冲突的时候,返回错误的信息,让用户决定如何去做,乐观锁不会使用数据库提供的锁机制,一般的实现方式就是记录数据版本。
在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
乐观锁的实现不需要借助于数据库锁机制,只要就是两个步骤:冲突检测和数据更新,其中一种典型的是实现方法就是CAS(Compare and Swap),但可能会导致ABA问题,解决ABA问题的一个方法是通过一个顺序递增的version字段,版本号机制。
CAS:CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试,自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。
ABA 问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
//查询出商品库存信息,quantity = 3,version=1
select version from items where id=1
//修改商品库存为2
update items set quantity=2,version=2 where id=1 and version=1;
##乐观锁和悲观锁对比
乐观锁并不是真正的加锁,优点是效率高,缺点是更新失败的概率比较高;悲观锁依赖于数据库锁机制,更新失败的概率比较低,但是效率也低。
两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
CAS与synchronized的使用情景
简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
synchronized关键字
Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1)、修饰一个代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象,修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
2)、修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
3)、修改一个静态的方法:其作用的范围是整个静态方法,作用的对象是这个类的所有对象
4)、修改一个类:其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是对当前 Class 类上锁;
synchronized 关键字加到实例方法上是给对象实例上锁;
尽量不要使用 synchronized(String s),因为 JVM 中,字符串常量池具有缓存功能。
线程安全就是同步,
线程不安全就是异步
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统,挂起线程和恢复线程的操作都需要转入内核态中完成。
JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。,互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。
偏向锁:是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,偏向锁在无竞争的情况下会把整个同步都消除掉。会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步,对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失。
轻量级锁:若偏向锁失败,虚拟机并不会立即升级为重量级锁,而是膨胀为轻量级锁,本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量,轻量级锁的加锁和解锁都用到了CAS操作,轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁。
自旋锁:轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段,为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。如果锁被占用的时间短,那么效果当然就很好了!反之,相反!自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁,就应该挂起线程。自旋次数的默认值是10次
自适应自旋:在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了,有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测会越来越准确。
锁消除:指虚拟机即时编译器在运行时,对一些代码上要求同步但是被检测到不可能存在共享数据竞争的锁进行消除,锁消除的主要判断依据来源于逃逸分析的数据支持。
锁粗化:如果一些列的连续操作都对同一个对象反复加锁和解锁,甚加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗,会把多次对同一个个对象的加锁操作简化,只进行一次操作。
重量级锁:ReenTrantLock 和 synchronized
两者都是可重入锁,自己可以再次获取自己的内部锁。
synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API,synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
性能已不是选择标准
在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。
线程间的协作机制
wait 类方法用于阻塞当前线程,将当前线程挂载进 Wait Set 队列,notify 类方法用于释放一个或多个处于等待队列中的线程。
所以,这两个方法主要是操作对象的等待队列,也即是将那些获得锁但是运行期间缺乏继续执行的条件的线程阻塞和释放的操作,但是有一个前提大家需要注意,wait 和 notify 操作的是对象内置锁的等待队列,也就是说,必须在获得对象内置锁的前提下才能阻塞和释放等待队列上的线程。简单来说,这两个方法的只能在 synchronized 修饰的代码块内部进行调用,虽然阻塞队列和等待队列上的线程都不能得到 CPU 正常执行指令,但是它们却属于两种不同的状态,阻塞队列上的线程在得知锁已经释放后将公平竞争锁资源,而等待队列上的线程则必须有其他线程通过调用 notify 方法通知并移出等待队列进入阻塞队列,重新竞争锁资源。join 方法用于实现两个线程之间相互等待的一个操作。
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务
一.Java中的ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,以下是构造器中几个参数:
int corePoolSize:默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
int maximumPoolSize:它表示在线程池中最多能创建多少个线程;
long keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
TimeUnit unit:参数keepAliveTime的时间单位
BlockingQueue workQueue:一个阻塞队列,用来存储等待执行的任务
ThreadFactory threadFactory:线程工厂,主要用来创建线程;
RejectedExecutionHandler handler:表示当拒绝处理任务时的策略,有以下四种取值:
源代码解析
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService。
在ThreadPoolExecutor类中有几个非常重要的方法:
execute():这个方法可以向线程池提交一个任务,交由线程池去执行。
submit():这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果
shutdown():用来关闭线程池
shutdownNow():用来关闭线程池
二.深入剖析线程池实现原理
线程池的具体实现原理:
如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
线程池中的线程初始化:
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartCoreThread():初始化一个核心线程;
prestartAllCoreThreads():初始化所有核心线程
任务缓存队列及排队策略:
在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。
workQueue的类型为BlockingQueue,通常可以取下面三种类型:
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
任务拒绝策略:
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池的关闭:
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
setCorePoolSize:设置核心池大小
setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
三.使用示例
四.如何合理配置线程池的大小
线程中的任务最终是交给CPU的线程去处理的,而CPU可同时处理线程数量大部分是CPU核数的两倍,运行环境中CPU
的核数我们可以通过Runtime.getRuntime().availableProcessors()这个方法而获取。理论上来说核心池线程数量应该为
Runtime.getRuntime().availableProcessors()*2,那么结果是否符合我们的预期呢,可以来测试一下(本次测试测试的
是I/O密集型任务,事实上大部分的任务都是I/O密集型的,即大部分任务消耗集中在的输入输出。而CPU密集型任务主
要消耗CPU资源进行计算,当任务为CPU密集型时,核心池线程数设置为CPU核数+1即可)
里面的东西很多都是我参考自《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化里面的内容。
下面是一些具体的实例,理论知识学了,需要来点小的测试加深印象
双重效验锁机制
package com.zyu.springandmybatis.Thread;
//双重效验锁(double-checked locking) 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
// DCL 方式的单例需确保使用 volatile
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
// 先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (singleton == null) {
///类对象加锁
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
//饿汉模式,很饿很着急,所以类加载时即创建实例对象
class Singleton1 {
private static Singleton1 singleton = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return singleton;
}
}
//饱汉模式,很饱不着急,延迟加载,啥时候用啥时候创建实例,存在线程安全问题
class Singleton2 {
private static Singleton2 singleton;
private Singleton2() {
}
public static synchronized Singleton2 getInstance() {
if (singleton == null)
singleton = new Singleton2();
return singleton;
}
}
测试使用中断标识结束线程
package com.zyu.springandmybatis.Thread;
/**
* 输出字符串线程
* @author zyu
*/
class PrintStringThread extends Thread {
@Override
public void run() {
try {
for(int i=0; i<50; i++) {
System.out.println("test!"+i);
sleep(200);
}
} catch(InterruptedException e) {
//e.printStackTrace();
}
}
}
/**
* 使用中断标识结束线程
*/
public class TestInterrupt {
public static void main(String[] args) {
Thread t = new PrintStringThread();
//t.interrupt();
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
测试 join
package com.zyu.springandmybatis.Thread;
/**
*/
class PrintCharThread extends Thread {
private char c;
private int times;
public PrintCharThread(char c, int times) {
this.c = c;
this.times = times;
}
@Override
public void run() {
for(int i=0; i<times; i++) {
if(i!=0&&i%10==0) {
System.out.println();
}
System.out.print(c);
System.out.print(' ');
}
}
}
public class TestPrintCharThread {
public static void main(String[] args) throws InterruptedException {
/*//创建线程组
ThreadGroup tg = new ThreadGroup("线程组");
//循环创建10个线程
for(int i=0; i<10; i++) {
//创建线程,并添加到线程组中
Thread t = new Thread(tg, new PrintCharThread((char)('A'+i), 100));
//启动线程
t.start();
}
//循环判断线程组中是否存在活动线程
while(tg.activeCount()>0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("\n程序执行完毕!");*/
//创建线程对象
Thread t1 = new PrintCharThread('渝', 100);
Thread t2 = new PrintCharThread('川', 100);
t1.start();
t2.start();
//当前线程等待线程t1、t2执行结束
t1.join();
t2.join();
System.out.println("\n程序执行完毕!");
}
}
创建一个线程安全的线程
package com.zyu.springandmybatis.Thread;
public class TestThreadSafe {
public static void main(String[] args) {
Person p = new Person();
Thread t1 = new Thread(p, "线程1");
Thread t2 = new Thread(p, "线程2");
Thread t3 = new Thread(p, "线程3");
t1.start();
t2.start();
t3.start();
}
}
class Person implements Runnable {
private int count = 50;
//线程不安全
public void run() {
while (true) {
if (count == 0)
break;
// Thread.sleep(100)是为了增加线程不安全的概率
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + ".." + count--);
}
}
//线程安全
// public void run() {
// while (true) {
// synchronized (this) {
// if (count == 0)
// break;
//// Thread.sleep(100)s是为了增加线程不安全的概率
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread() + ".." + count--);
// }
// }
// }
}
经典的消费者生产者模型测试
package com.zyu.springandmybatis.Thread.线程协作.生产者消费者模型;
import java.util.ArrayList;
import java.util.List;
public class Repository {
private List<Integer> list = new ArrayList<>();
private int limit = 10; //设置仓库容量上限
public synchronized void addGoods(int count) throws InterruptedException {
while (list.size() == limit) {
//达到仓库上限,不能继续生产
wait();
}
list.add(count);
System.out.println("生产者生产产品:" + count);
//通知所有的消费者
notifyAll();
}
public synchronized void removeGoods() throws InterruptedException {
while (list.size() <= 0) {
//仓库中没有产品
wait();
}
int res = list.get(0);
list.remove(0);
System.out.println("消费者消费产品:" + res);
//通知所有的生产者
notifyAll();
}
//测试生产者消费者模型
public static void main(String[] args) throws InterruptedException {
Repository repository = new Repository();
Thread producer = new Producer(repository);
Thread consumer = new Customer(repository);
producer.start();
consumer.start();
producer.join();
consumer.join();
System.out.println("main thread finished..");
}
}
//消费者
class Customer extends Thread {
Repository repository = null;
public Customer(Repository p) {
this.repository = p;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep((long) (Math.random() * 500));
repository.removeGoods();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer extends Thread {
Repository repository = null;
public Producer(Repository p) {
this.repository = p;
}
@Override
public void run() {
int count = 1;
while (true) {
try {
Thread.sleep((long) (Math.random() * 500));
repository.addGoods(count++);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试线程池的使用
package com.zyu.springandmybatis.Thread.线程池;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*
* 测试线程池的使用
*/
public class test1 {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
码字不易,希望对你有所帮助。
来源:CSDN
作者:很有想法的小伙子
链接:https://blog.csdn.net/qq_40388552/article/details/103762708