Java并发编程的艺术(一)
并发编程的目的是为了让程序运行的更快,但是并不是启动更多的线程就能让程序更大限度地并发执行。--例如上下文切换的问题,死锁的问题,受限于软件和硬件的资源问题。
单核处理器也可以支持多线程编码:
CPU通过给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU通过不停地切换线程执行,让我们感受到多个线程同时执行,一般时间片的大小为几十毫秒(ms).
CPU通过时间片分配算法来循环执行任务,当一个任务执行一定的时间片后就会切换另一个任务,在切换钱会保存上一个任务的状态,一边下一切换回去的时候可以再加载这个任务的状态。所以人物从保存到再次加载的过程称为一次上下文切换。
多线程不一定比单线程快,在操作量不大的情况下,线程的创建和上下文切换反而使多线程比单线程更加慢。
减少上下文切换的方式:
1、无锁并发编程。多线程竞争锁的时候,会引起上下文切换,尽可能避免使用锁可以减少上下文的切换:如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
2、CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
3、使用最少的线程数量。大量的空闲线程(waitting状态),除了增加创建开销,还有切换上下文的开销。在任务很少的情况下尽量减少不必要的线程。
4、协程。在单线程里实现多任务的调度,并在单线程里维持多任务间的切换。
死锁
一个死锁的发现过程Demo
贴入死锁Demo代码
public class DeadLockDemo {
private static String A = "A";
private static String B = "B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
private void deadLock() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (A) {
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("1");
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (B) {
synchronized (A) {
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
}
}
一、获取pid
方法一:在jdk/bin目录下打开控制台,敲击jps-v找到自己的程序的Pid
方法二:在jdk/bin目录下有一个叫做jvisualvm的可执行文件,打开
二、用jstack查看日志(这里dump似乎跟我的电脑八字犯冲,stackoverflow上的方法都用了也不行,所以选择曲线救国,打印日志。)
在jdk/bin下打开cmd,输入 jstack -l 5444 > jstack.log
(jstack -l pid > 文件名.后缀)
文件太大就不放上来了,但是其中有两段引起我的注意。
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00e75444 (object 0x0f6b7850, a java.lang.String),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00e76164 (object 0x0f6b7870, a java.lang.String),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.jathonkatu.day20190716.DeadLockDemo$2.run(DeadLockDemo.java:37)
- waiting to lock <0x0f6b7850> (a java.lang.String)
- locked <0x0f6b7870> (a java.lang.String)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at com.jathonkatu.day20190716.DeadLockDemo$1.run(DeadLockDemo.java:27)
- waiting to lock <0x0f6b7870> (a java.lang.String)
- locked <0x0f6b7850> (a java.lang.String)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
分析一下:
线程1在等待锁0x0F6b7850,正在被锁0x0f6b7870锁住,且两者都是String类型(两个String在常量池中的地址)。
相反,线程2在等待锁0x0f6b7870,正在被锁0x0F6b7850锁住。
从上面不难得出,事实上就是一个死锁的行为,结合代码就不难分析,一个持有String对象A的锁,请求String对象B的锁,一个持有String对象B的锁,请求String对象A的锁。
避免死锁的常见方法:
1、避免一个线程同时获取多个锁
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3、尝试使用定时锁,ReentrantLock类中有个方法tryLock(long timeout,TimeUnit unit)来代替内部锁机制。
4、对于数据库锁,加锁和解锁必须在一个数据库链接里,否则会出现失败的情况。(释放锁失败抛异常后仍然持有锁)
资源限制
在并发编程时,程序的执行速度受限于计算机硬件资源或软件资源。
硬件资源的限制有:带宽的上传/下载速度,硬盘读写速度和CPU的处理速度。
软件资源的限制有:数据库的连接数和socket连接数等。
资源限制引发的问题
将代码串行执行的部分改成并行执行固然能加快代码运行,但如果受限于资源后,期望并行执行的代码其实还是串行执行。并且不仅仅不会加快代码执行,反而会更慢,因为增加了上下文切换和资源调度时间。
如何解决资源限制问题
考虑使用集群并行执行程序,如ODPS、Hadoop或者自己搭建的服务器集群。不同的机器处理不同的数据,可以通过“数据ID%机器数”,计算计算机编号,根据不同的编号用不同的机器处理。
资源限制情况下进行并发编程
根据不同的资源限制调整程序的并发度。有数据库操作时,设计数据库连接数,如果Sql执行非常快,而线程数量比数据库连接数大很多,则某些线程会被阻塞,等待数据库链接。如下载文件就依赖于贷款和硬盘读写速度。
- 基于pytest集成selenium
- Android应用程序使用两个LinearLayout编排5个Button控件
- Robot Framework | 02 从抛弃RIDE开始创建你的RFS测试
- [接口测试 - http.client篇] 14 源码初探及其工作机制分析
- 接口测试 | 25 requests + pytest测试实例
- 接口测试 | 22 requests基础入门
- 图的存储结构的实现(C/C++实现)
- 树和二叉树的存储结构的实现(C/C++实现)
- Selenium3源码之异常模块篇
- 移位密码原理及算法实现
- 排序算法的实现(C/C++实现)
- [开源] 分享导出博客园文章成本地 Markdown 文件存储的工具
- 单表代替密码原理及算法实现
- 【Android开发学习笔记之一】5大布局方式详解
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 腾讯云TcaplusDB基础能力介绍
- 游戏架构上云实战
- 【JUC】CyclicBarrier的了解和使用
- 完美解决-RuntimeError: CUDA error: device-side assert triggered
- springmvc之异常处理SimpleMappingExceptionResolver
- 剑指offer(13-15)题解
- 【leetCode】青蛙跳台问题(这只青蛙会托马斯大旋转)day07
- 【leetCode】斐波那契数列day06
- 剑指offer(61-67)题解
- 宇智波程序笔记8-【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!
- 情感分析数据预处理过程
- java的内部类和静态内部类(嵌套类)
- python爬取B站视频弹幕分析并制作词云
- mybatis扩展之自定义类型处理器处理枚举类型
- IMDB影评数据集预处理(使用word2vec)