个人博客网: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

相关扩展:Java项目中使用log记录日志的一些总结

二、多线程并发最佳实践

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并发编程 (十) 多线程并发拓展的更多相关文章

  1. java并发编程--第一章并发编程的挑战

    一.java并发编程的挑战 并发编程需要注意的问题: 并发编程的目的是让程序运行的更快,然而并不是启动更多的线程就能让程序最大限度的并发执行.若希望通过多线程并发让程序执行的更快,会受到如下问题的挑战 ...

  2. java并发编程与高并发解决方案

    下面是我对java并发编程与高并发解决方案的学习总结: 1.并发编程的基础 2.线程安全—可见性和有序性 3.线程安全—原子性 4.安全发布对象—单例模式 5.不可变对象 6.线程封闭 7.线程不安全 ...

  3. Java并发编程系列-(1) 并发编程基础

    1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运 ...

  4. Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁

    Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...

  5. 并发编程概述--C#并发编程经典实例

    优秀软件的一个关键特征就是具有并发性.过去的几十年,我们可以进行并发编程,但是难度很大.以前,并发性软件的编写.调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程.新版.NET 中的程序库和语 ...

  6. Java并发编程、多线程、线程池…

    <实战java高并发程序设计>源码整理https://github.com/petercao/concurrent-programming/blob/master/README.md Ja ...

  7. Java并发编程之多线程

    线程 进程/线程/协程/管程 进程:操作系统会以进程为单位,分配系统资源(CPU时间片.内存等资源),是资源分配的最小单位 进程间通信(IPC): 管道(Pipe) 命名管道(FIFO) 消息队列(M ...

  8. Java并发编程,多线程[转]

    Java并发编程 转自:http://www.cnblogs.com/dolphin0520/category/602384.html 第一个例子(没有阻塞主线程,会先输出over): package ...

  9. 4、Java并发性和多线程-并发编程模型

    以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现. ...

随机推荐

  1. u-boot spl 学习总结

    什么是SPL? SPL(secondary program loader)是一个十分小的bin文件,它是用来引导主u-boot文件.对于一些SRAM很小的SOC,无法一次性加载ROM中的bootloa ...

  2. RabbitMQ的发布订阅模式(Publish/Subscribe)

    一.发布/订阅(Publish/Subscribe)模式 发布订阅是我们经常会用到的一种模式,生产者生产消息后,所有订阅者都可以收到.RabbitMQ的发布/订阅模型图如下: 1.该模式下生产者并不是 ...

  3. 如何使用 frp 实现内网穿透

    这有一个专注Gopher技术成长的开源项目「go home」 背景 作为一名程序员,家里多多少少会有一些落了灰的电脑,如果把闲置的电脑变成服务器,不仅有良好的配置,还能用来做各种测试,那就再好不过了. ...

  4. Android CodeReview 些许总结

    CodeReview些许总结 1:使用Handler的时候,使用handler.post(Runnable);,hanler与类尽量保持弱引用关系,或者使用静态的handler对象 public Ha ...

  5. java ->EL技术&JSTL技术

    EL技术 EL 表达式概述 EL(Express Lanuage)表达式可以嵌入在jsp页面内部,减少jsp脚本的编写,EL出现的目的是要替代jsp页面中脚本(java代码)的编写. EL从域中取出数 ...

  6. 2018-08-26 jQuery与javaScript的区别及核心方法

    1.jq对象就是js new Object 生成的普通对象. 2.jq对象与js对象,他们的方法不能共用! 3.jq对象与js对象的相互转化: js对象转jq对象 -> $(js_obj);// ...

  7. call(),apply(),bind() 区别和用法

    call call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表.当第一个参数为null.undefined的时候,默认指向window. var arr = [1, 2, 3, 8 ...

  8. ssh别名登录,非常适合从跳板机登录其他主机

    一般连主机会是这样的: ssh admin@IP 端口变了的话还要加上端口号 ssh admin@IP -p 10022 可以用ssh别名简化这个操作: vim .ssh/config 想要全局生效的 ...

  9. cpu 风扇的影响

    公司电脑前段时间莫名死机,跳电,看了一下,电源是杂牌货.功率也只有200w 出头点,我一开始怀疑是电源问题,后来看了一下温度,傻眼了. 正好有图存着,贴出来. cpu 使用率高的时候,温度能有90多度 ...

  10. NodeJS的概述

    1.NodeJS概述 基于谷歌V8引擎,运行在服务器端的环境 对比JS和NodeJS (1)JS运行在浏览器端,存在多种浏览器解释器,容易产生兼容性的问题:而NodeJS运行在服务器端,只有V8引擎一 ...