Java并发编程 (十) 多线程并发拓展
个人博客网:https://wushaopei.github.io/ (你想要这里多有)
一、死锁
1、死锁的定义
所谓的死锁是指两个或两个以上的线程在等待执行的过程中,因为竞争资源而造成的一种互相等待的现象。若不受外力作用,他们都将无法推进下去。此时,处于系统中所处的状态就是死锁。
2、发生死锁所必须具备的条件:
互斥条件:它是指进程对所分配的资源进行排他性的使用,在一定时间内,某资源只由一个进程在用,如果此时还有其他进程请求资源,请求者只能等待。直到占有资源的进程用完或释放之后才可以继续使用。
请求和保持条件:它是指进程已经保持了至少一个资源,又提出了新的资源请求,该资源已被其它进程占用。此时,请求进程阻塞,对自己或者其它资源保持不放;
不剥夺条件:指进程获取资源在用完之前不能被剥夺,只能在使用完后再由自己释放;
环路等待条件:发生死锁的时候,一定存在一个进程的资源是一个环形的链。
3、代码演示死锁:
/**
* 一个简单的死锁类
* 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒
* 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒
* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;
* td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;
* td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
*/
@Slf4j
public class DeadLock implements Runnable {
public int flag = 1;
//静态对象是类的所有对象共享的
private static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
log.info("flag:{}", flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
log.info("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
log.info("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
//td2的run()可能在td1的run()之前运行
new Thread(td1).start();
new Thread(td2).start();
}
}
执行并打印结果:
21:13:12.979 [Thread-1] INFO com.mmall.concurrency.example.deadLock.DeadLock - flag:0
21:13:12.979 [Thread-0] INFO com.mmall.concurrency.example.deadLock.DeadLock - flag:1
Process finished with exit code 1
二、多线程并发最佳实践
1、使用本地变量:应该使用本地变量,而不是创建一个类或实例变量。通常情况下,开发人员使用对象实例作为一个类,可以节省内存,并可以重用。因为每次在方法中创建新的本地变量会消耗很多内存。
2、使用不可变类:如 String 、Integer等,一旦创建就不会改变了,不可变类可以降低代码中的同步数量
3、最小化锁的作用域范围: S=1/(1-a+a/n)
4、使用线程池的Executor,而不是直接new Thread执行:
创建一个线程的代价是昂贵的,如果你要得到一个可伸缩的java应用,那么你需要使用线程池,从线程池来管理线程,jdk中提供了各种方法实现
5、宁可使用同步也不要使用线程的wait和notify
从java1.5以后增加了许多的同步工具,要优先使用同步工具,而不是使用wait和notify方法
6、使用BlockingQueue实现生产-消费模式
7、使用并发集合而不是加了锁的同步集合
8、使用Semaphore创建有界的访问
9、宁可使用同步代码块,也不使用同步的方法
10、避免使用静态变量
三、Spring与线程安全
Spring bean : singleton 、 prototype
无状态对象
无状态对象:就是自身没有状态的对象,当然也就不会因为多个线程交替调度破坏自身的状态而导致安全问题。无状态对象包括经常使用的DTO、VO,只作为数据实体的模型对象。
四、HashMap与ConcurrentHashMap解析
1、HashMap 的数据结构:
在java编程语言中,最基本的结构有两种,一个是数组,另外一个就是指针,即引用。

HashMap的底层就是一个数组结构,数组的每一项又是一个链表,当我们新建HashMap的时候就会初始化一个数组出来。
HashMap有两个参数影响它的性能,分别是初始容量和加载因子。

由源码可知,HashMap的初始容量为16,

由上图中,可知HashMap的默认加载因子为0.75.

当HashMap 的长度达到的容量长度满足初始值的0.75时,就会调用resize()方法进行扩容:

我们也可以根据需要指定HashMap的初始化容量和加载因子。
2、HashMap 的线程安全性:
HashMap是线程不安全的,主要体现在前面的resize()方法,它可能会导致死循环的发生,并且在使用迭代器的时候fasfree。当HashMap的长度超过了它的capacity * loadFactor时,就需要对它进行扩容,具体方法是:它要创建一个新的长度为原来容量的两倍的数组。它保证新的容量为2的N次方,从而保证寻址的方式依然适用。同时,它原来的数组会全部插入到新的数组中。这个过程我们称之为rehash。
这个方法并不保证线程安全,而且在多线程并发调用时可能陷入死循环。
3、HashMapd的ReHash 操作示意图:
单线程下的ReHash操作:

多线程下的ReHash操作:



4、ConcurrentHashMap的底层数据结构:

5、ConcurrentHashMap和HashMap的不同点:
- ConcurrentHashMap是线程安全的,HashMap是线程不安全的;
- HashMap允许key\value为空,而ConcurrentHashMap是不允许的
6、ConcurrentHashMap 改进后

Java7以后针对并发访问引入了Segment这个结构,实现了分段锁,提高并发度,与Segment的个数是相等的。Java8以后为了进一步提高并发性,它废弃了这里面的分段锁方案,并且直接使用一个大的数组,同时为了提高hash碰撞下的寻址做了性能优化。
Java8以后它的链表的长度超过一定的值(默认为8),这里的链表就会变成了红黑树。
Java并发编程 (十) 多线程并发拓展的更多相关文章
- java并发编程--第一章并发编程的挑战
一.java并发编程的挑战 并发编程需要注意的问题: 并发编程的目的是让程序运行的更快,然而并不是启动更多的线程就能让程序最大限度的并发执行.若希望通过多线程并发让程序执行的更快,会受到如下问题的挑战 ...
- java并发编程与高并发解决方案
下面是我对java并发编程与高并发解决方案的学习总结: 1.并发编程的基础 2.线程安全—可见性和有序性 3.线程安全—原子性 4.安全发布对象—单例模式 5.不可变对象 6.线程封闭 7.线程不安全 ...
- Java并发编程系列-(1) 并发编程基础
1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运 ...
- Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁
Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...
- 并发编程概述--C#并发编程经典实例
优秀软件的一个关键特征就是具有并发性.过去的几十年,我们可以进行并发编程,但是难度很大.以前,并发性软件的编写.调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程.新版.NET 中的程序库和语 ...
- Java并发编程、多线程、线程池…
<实战java高并发程序设计>源码整理https://github.com/petercao/concurrent-programming/blob/master/README.md Ja ...
- Java并发编程之多线程
线程 进程/线程/协程/管程 进程:操作系统会以进程为单位,分配系统资源(CPU时间片.内存等资源),是资源分配的最小单位 进程间通信(IPC): 管道(Pipe) 命名管道(FIFO) 消息队列(M ...
- Java并发编程,多线程[转]
Java并发编程 转自:http://www.cnblogs.com/dolphin0520/category/602384.html 第一个例子(没有阻塞主线程,会先输出over): package ...
- 4、Java并发性和多线程-并发编程模型
以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现. ...
随机推荐
- 【Spark】SparkStreaming和Kafka的整合
文章目录 Streaming和Kafka整合 概述 使用0.8版本下Receiver DStream接收数据进行消费 步骤 一.启动Kafka集群 二.创建maven工程,导入jar包 三.创建一个k ...
- 【FreeRTOS学习06】深度解剖中断与任务之间同步的具体使用场景
嵌入式系统中中断是必不可少的一部分: [FreeRTOS实战汇总]小白博主的RTOS学习实战快速进阶之路(持续更新) 文章目录 1 前言 2 中断特点 3 延迟中断处理 3.1 信号量的使用 3.2 ...
- [uva_la7146 Defeat the Enemy(2014 shanghai onsite)]贪心
题意:我方n个军队和敌方m个军队进行一对一的对战,每个军队都有一个攻击力和防御力,只要攻击力不小于对方就可以将对方摧毁.问在能完全摧毁敌方的基础上最多能有多少军队不被摧毁. 思路:按防御力从大到小考虑 ...
- [hdu5213]容斥原理+莫队算法
题意:给一个序列a,以及K,有Q个询问,每个询问四个数,L,R,U,V, 求L<=i<=R,U<=j<=V,a[i]+a[j]=K的(i, j)对数(题目保证了L <= ...
- Mysql 常用语句实战(3)
前置 sql 语句 用来创建表.插入数据 ; DROP TABLE IF EXISTS dept_;-- 部门表 DROP TABLE IF EXISTS emp_;-- 部门表 ; SELECT @ ...
- HDU 2017 (水)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2017 题目大意:给你段字符串,求出字符串中含有数字字符的个数 解题思路: 字符串输入输出的基本应用:h ...
- curl发送请求
一.get请求 curl "http://www.baidu.com" 如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地 curl -i "http:// ...
- javaweb学习之路(3)Cookie
1.Cookies的原理 1)首先浏览器向服务器发出请求. 2)服务器就会根据需要生成一个Cookie对象,并且把数据保存在该对象内. 3)然后把该Cookie对象放在响应头,一并发送回浏览器. 4) ...
- java ->EL技术&JSTL技术
EL技术 EL 表达式概述 EL(Express Lanuage)表达式可以嵌入在jsp页面内部,减少jsp脚本的编写,EL出现的目的是要替代jsp页面中脚本(java代码)的编写. EL从域中取出数 ...
- 黑马程序员_毕向东_Java基础视频教程——逻辑运算符(随笔)
逻辑运算符 逻辑运算符用于连接 boolean 型的表达式 & : 只要两边都是 boolean 表达结果,有一个为 false ,则结果就是 false 只要两边都为 true 则结果就为 ...