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执行非常快,而线程数量比数据库连接数大很多,则某些线程会被阻塞,等待数据库链接。如下载文件就依赖于贷款和硬盘读写速度。
- Android中Services之异步IntentService
- 使用GitHub搭建个人博客
- 这个用来玩儿游戏的算法,是谷歌收购DeepMind的最大原因
- asp.net安全检测工具 --Padding Oracle 检测
- Android中Services简析
- VUE 入门基础(2)
- VUE 入门基础(1)
- AndroidManifest.xml配置文件 android.theme大全权限设置Android Permission中英对照
- Reactive框架:简化异步及事件驱动编程
- 微信跳一跳小游戏外挂分析
- 承载WCF 数据服务
- 常用正则表达式
- StreamInsight 基本概念
- git 常用命令
- 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 文档注释
- 在 Nowin 下运行 ASP.NET 5 Beta 2
- Bytom侧链Vapor源码浅析-节点出块过程
- Kubernetes Pod OOM 排查日记
- Netty之旅:你想要的NIO知识点,这里都有!
- (数据科学学习手札92)利用query()与eval()优化pandas代码
- Spring Boot 集成 Elasticsearch 实战
- Python之错误和异常、模块(基础系列第四篇)
- Spark存储Parquet数据到Hive,对map、array、struct字段类型的处理
- 为什么这条异常没有上报? HTTP 429
- 三问Spring事务:解决什么问题?如何解决?存在什么问题?
- 从 OAuth2 服务器获取授权授权
- NHibernate 代码映射实体类
- 使用 Castle Windsor 实现 Web API 依赖注入
- SparkSQL与Hive metastore Parquet转换
- Spark中广播变量详解以及如何动态更新广播变量