目录
0、插播2020CSDN博客之星投票新闻
一、开篇
二、并发与并行
三、并发程序带来关键问题
1、数据竞争
2、死锁
3、活锁
4、资源不足
5、优先权反转
四、Java并发API(详细)
1、基本并发类
2、同步机制
3、执行器
4、Fork/Join框架
5、并发数据结构
五、并发设计模式
1、信号模型
2、会合模式
3、互斥模式
4、读写锁模式
5、线程池模式
6、线程局部存储模式
六、最后总结
近日(1月11日-1月24日),2020CSDN博客之星评选正在火热进行中,作为码龄1年的小白有幸入选Top 200,首先很感谢CSDN官方把我选上,本来以为只是来凑热闹,看大佬们PK ^_^#。
综合过去4天大佬们战况,前十名大佬坐得很稳,不得不令人佩服点赞!真正的实力可以看出,文章数量不重要,更重要的是质量!一切用数据说话,如图:
截至 2021-01-15 14:40:01
看了大佬的惊人数据,与我差距甚大,不禁感慨,小白只希望进Top 100,希望看到这条新闻的大佬、朋友们、粉丝们、和我一样的小白们,能顶我一下,为我投全票,新年一起加油进步,万事如意!^_^#
投票地址:https://bss.csdn.net/m/topic/blog_star2020/detail?username=charzous
或者扫码投票:
重点:每一个投票都会被记录,投了票的后面找Charzous帮忙也容易了(疯狂暗示投票拉票)
即日起到24号,每天都可以投票哦,票数越多,贡献排行榜就越靠前,我就记住你的名字啦!
在我之前写的许多关于Java网络编程的博文中,已经初步使用了多线程的技术,是java并发的相关应用案例。而现在,需要学习一些关于并发程序设计的原理,弄懂来龙去脉,相对更加深入地理解并发设计原理。而且我发现,前面学习Java网络编程之后,有了实践性的理解,再学习其相关原理,比较容易理解原理方面的知识。
这一篇记录一下我学习的重要知识,整理出来感觉更容易看到,也是方便以后重温学习查看!
很奇妙,并发和并行两个概念在操作系统的课程上有学习过,当时只是在操作系统层面上理论的学习,未涉及实际应用场景和编程语言。现在重温知识进一步学习,发现理解起来更实际了。
并发和并行概念有许多定义,整理一下好理解的几个:
并发是指2个或多个活动在同一时间间隔内发生,在多道程序设计中,是指在同一时间段内有多个程序任务同时运行,也就是宏观上同时执行,微观上是串行的。
并行是指在微观上多个任务同时执行,发生在同一个时间点上。显然,要求多个程序任务并行运行,就需要多个处理器,在单处理器系统中,只有并发而无并行
在单个处理器上采用单核执行多个任务即为并发,操作系统的调度程序会很快从一个任务切换到另一个任务,因此看起来所有任务都是同时运行的。而同一时间在不同处理器或处理器核心上同时执行多个任务,就是并行。
最关键的还是并发原理以及设计,在并发中一个重要概念“同步”由此引出。
同步是一种协调两个或多个任务以获得预期结果的机制。包括:
- 控制同步:任务依赖关系。当一个任务的执行需要依赖于另一个任务的结束时,第二个任务不能在前者完成之前开始。
- 数据访问同步:当两个或多个任务访问共享变量时,在任意时间里,只有一个任务可以访问该变量。
与同步密切相关的一个概念是临界段,它是一段代码,用来保证在任意时间内只有一个任务能够访问共享资源。而互斥就是用来保证这个临界段的机制。
并发程序的编写设计需要在同步/互斥机制上做许多工作,才能保证并发程序正确执行。所以,需要关注下面几个关键问题。
简单来说,就是多个任务对共享变量进行写入操作,而没有同步机制的临界段作为约束,程序就存在数据竞争。
public class Account{
private float balance;
public void modify(float difference){
float value=this.balance;
this.balance=value+difference;
}
}
例如,上面的例子,如果存在两个任务执行同一个Account对象进行modify操作,初始balance=1000,最终应该是3000,但是如果两个任务同时执行了modify方法的第一条语句,又同时执行第二条语句,结果变为2000。因此,Account类不是线程安全的,没有实现原子操作和同步机制。
简单来讲,就是两个或多个任务正在等待另一线程释放的某个资源,而这个线程也正在等待前面的任务释放的资源,这样的并发程序就出现了死锁。
完全满足以下条件时候,就会导致死锁。
- 互斥条件:死锁中的资源是不可共享的,一个时间段内只有一个任务可以使用该资源。
- 请求和保持条件:任务已经保持了至少一个资源,但又提出新的请求,而该资源需要等待释放,此时该任务阻塞等待,同时不会释放自己获得的资源。
- 非剥夺条件:资源只能被那些持有他们的任务释放。
- 循环等待条件:任务1正等待任务2占用的资源,任务2又正在等待任务3占用的资源,……,这样出现循环等待。
处理死锁的方法有:预防、避免、检测、解除。这四个基本方法都有具体实现的算法。
如果系统中有两个任务,总是因为对方的行为而改变自己的状态,就会出现活锁。
举个栗子,任务1、2都需要资源1、2,此时任务1拥有资源1并加锁,任务2拥有资源2并加锁。当他们无法访问所需资源时候,就释放自己的资源并开始新的循环,这样无限持续下去,两个任务都不会结束自己的执行过程。
活锁就是一种资源不足的情况,当任务在系统中无法获取所需资源继续执行任务。
解决方法就是要确保公平原则。
当一个低优先权任务持有高优先权任务所需资源时,就会发生优先权反转,低优先权的任务会提前执行。
java包含了丰富的并发API,在并发程序设计时候可以灵活使用。
- Thread类:描述了执行并发Java应用程序的所有线程。
- Runnable接口:Java中创建并发程序的另一种方式。
- ThreadLocal类:用于存放从属于某个线程的变量。(没有同步机制时使用)
- ThreadFactory接口:实现Factory设计模式的基类,用以创建定制线程。
Java并发的同步机制支持定义访问某个共享资源的临界段;在某一共同点上同步不同的任务。
- sychronized关键字:可以在一个代码块或完整方法中定义一个临界段。
- Lock接口:提供更丰富灵活的同步操作。其中ReentantLock用于实现一个条件关联锁;ReentantRead-WriteLock将读写操作分离;StampedLock包括了控制读/写访问的模式,是Java8新增的特性。
以下几个比较陌生:
- CountDownLatch类:允许多个任务等待多项操作的结束。
- CyclicBarrier类:允许多线程在某个共同点上进行同步。
- Phaser类:允许控制那些被分为多阶段的任务的执行。所有任务在完成当前阶段分任务前,不能进入下个阶段。
- Executor接口和ExecutorService接口:包括共有的execute方法。
- ThreadPoolExecutor类:可以定义线程池执行器任务的最大数目。
- ScheduledThreadPoolExecutor类:一种特殊的执行器,在一段延迟之后执行任务或周期性执行任务。
- Callable接口:提供返回值的Runnable接口的代替接口。
- Future接口:包含获取Callable返回值并控制其状态的方法。
该框架定义了一种特殊的执行器,尤其针对分治求解问题,提供了一种优化机制,开销小。主要类和接口:
- ForkJoinPool:实现了用于运行任务的执行器。
- ForkJoinTask:可以在上述类中执行的任务。
- ForkJoinWorkerThread:准备在1类中执行任务的线程。
Java中常用的数据结构如:ArrayList、Map、HashMap等都不能在并发程序中使用,因为没有采取同步机制,不是线程安全的。如果自行采用同步机制,程序计算开销增大。
因此,Java提供了并发程序中特殊的数据结构,属于线程安全,主要有两个类别:
常用的并发数据结构(线程安全):
- ConcurrentHashMap:非阻塞哈希表(关键字Concurrent开头的其他数据结构)
- ConcurrentLinkedDeque:非阻塞型列表
- LinkedBlockQueue:阻塞型队列
- CopyOnWriteArrayList:读读共享、写写互斥、读写互斥。
- 实现了List接口
- 内部持有一个ReentrantLock lock = new ReentrantLock();
- 底层是用volatile transient声明的数组 array
- 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
5.PriorityBlockingQueue:阻塞型队列,基于优先级对元素进行排序。
6.AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference:基本Java数据类型的原子实现。(可使用原子变量代替同步)
实现该模式课采用信号量或者互斥,Java中的ReentrantLock或Semaphore,或者Object类的wait和notify方法。
public void task1(){
section1();
commonObject.notify();
}
public void task2(){
commonObject.wait();
section2();
}
section2方法总是在section1之后执行。
信号模式的推广,第一个任务将等待第二个任务某个活动的完成,而第二个任务也在等待第一个任务某个活动的完成,区别在于使用到两个对象。
public void task1(){
section1_1();
commonObject1.notify();
commonObject2.wait();
section1_2();
}
public void task2(){
section2_1();
commonObject2.notify();
commonObject1.wait();
section2_2();
}
其中的语句顺序不能修改,否则可能出现死锁。
section2_2总是在section1_1后执行, section1_2总是在section2_1后执行.
互斥机制用以实现临界段,确保操作相互排斥。
public void task1() {
preSection();
try {
lockObject.lock();//临界段开始
section();
}catch (Exception e){
……
}finally {
lockObject.unlock();//临界段结束
postSection();
}
}
该模式定义了一种特殊的锁,包含两个内部锁:一个用于读操作,一个用于写操作。
锁的特点:读读共享、写写互斥、读写互斥。
Java并发API的ReentrantReadWriteLock类实现了这种模式
该模式应用广泛,减少了每次为每个任务创建线程的开销,它是由一个线程集合和一个待执行任务队列构成。ExceutorService接口可以实现该模式。
Java中的ThreadLocal类实现了线程局部变量,可以使用线程局部存储,每个线程会访问该变量不同实例。
并发算法设计遵循:
1、使用线程安全的Java并发API
2、在静态类和共享场合使用局部线程变量
3、避免死锁:对锁排序
4、使用原子变量
这一篇我记录了学习并发设计原理的关键知识,还有一些常用重要的Java并发编程API,在学习理论知识后,对这个部分有了更深层的理解,如果能够加上实践项目或者具体地案例分析,才能算真正掌握。因此,我也找了比较实际的案例进行实践——文件搜索,将在下一篇的博客进行详细记录,希望理论+实践结合,知识的理解更进一步!
如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/112603639