Java并发编程的艺术[1]

冷暖自知 提交于 2020-11-02 08:19:10

    昨天阅读翻译了CompletableFuture的源码,目前百度,有道,基本是翻译效果一般,Google翻译比较准确,源码有很多注释,写个小测试类将其去掉,另外获得了《Java并发编程的艺术》PDF版,因为需要测试demo,就要转word,又找了个小测试类转成word,效果不错。参考《Java并发编程的艺术》

 1.上下文切换

个人理解:CPU需要暂停当前任务,执行另一个任务,另一个任务完成后再执行当前任务,我们知道时钟中断导致cpu切换进程

原文:CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

2.多线程一定快吗?

书中demo

public class Question1 {private static final long count = 10000L;public static void main(String[] args) throws InterruptedException {            concurrency();            serial();new CompletableFuture<>();        }private static void concurrency() throws InterruptedException {long start = System.currentTimeMillis();            Thread thread = new Thread(            ()->{int a = 0;for (long i = 0; i < count; i++) {                    a += 5;                }            });            thread.start();int b = 0;for (long i = 0; i < count; i++) {                b--;            }long time = System.currentTimeMillis() - start;            thread.join();            System.out.println("concurrency :" + time+"ms,b="+b);        }private static void serial() {long start = System.currentTimeMillis();int a = 0;for (long i = 0; i < count; i++) {                a += 5;            }int b = 0;for (long i = 0; i < count; i++) {                b--;            }long time = System.currentTimeMillis() - start;            System.out.println("serial:" + time+"ms,b="+b+",a="+a);        }}

1.1.2 测试上下文切换次数和时长

下面我们来看看有什么工具可以度量上下文切换带来的消耗。

·使用Lmbench3[1]可以测量上下文切换的时长。

·使用vmstat可以测量上下文切换的次数。

下面是利用vmstat测量上下文切换次数的示例。

个人操作如下

vmstat(Virtual Memory Statistics 虚拟内存统计) 命令用来显示Linux系统虚拟内存状态,也可以报告关于进程、内存、I/O等系统整体运行状态


vmstat 1


CS(Content Switch)表示上下文切换的次数,从上面的测试结果中我们可以看到,上下文

每1秒切换1000多次。

示例1:vmstat 命令说明

[root@zhaokk opt]# vmstatprocs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st3  0      0 1201060   7204 234044    0    0     1     5    8   15  1  1 99  0  0

Procs(进程)

r:

运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1)

b

等待IO的进程数量。

Memory(内存)

swpd

使用虚拟内存大小,如果swpd的值不为0,但是SI,SO的值长期为0,这种情况不会影响系统性能。

free

空闲物理内存大小。

buff

用作缓冲的内存大小。

cache

用作缓存的内存大小,如果cache的值大的时候,说明cache处的文件数多,如果频繁访问到的文件都能被cache处,那么磁盘的读IO bi会非常小。

Swap

si

每秒从交换区写到内存的大小,由磁盘调入内存。

so

每秒写入交换区的内存大小,由内存调入磁盘。

注意:内存够用的时候,这2个值都是0,如果这2个值长期大于0时,系统性能会受到影响,磁盘IO和CPU资源都会被消耗。有些朋友看到空闲内存(free)很少的或接近于0时,就认为内存不够用了,不能光看这一点,还要结合si和so,如果free很少,但是si和so也很少(大多时候是0),那么不用担心,系统性能这时不会受到影响的。因为linux总是先把内存用光

IO

bi

每秒读取的块数

bo

每秒写入的块数

注意:随机磁盘读写的时候,这2个值越大(如超出1024k),能看到CPU在IO等待的值也会越大。

system(系统)

in

每秒中断数,包括时钟中断。

cs

每秒上下文切换数。

注意:上面2个值越大,会看到由内核消耗的CPU时间会越大。

CPU(以百分比表示)

us

用户进程执行时间百分比(user time) us的值比较高时,说明用户进程消耗的CPU时间多,但是如果长期超50%的使用,那么我们就该考虑优化程序算法或者进行加速。

sy:

内核系统进程执行时间百分比(system time) sy的值高时,说明系统内核消耗的CPU资源多,这并不是良性表现,我们应该检查原因。

wa

IO等待时间百分比 wa的值高时,说明IO等待比较严重,这可能由于磁盘大量作随机访问造成,也有可能磁盘出现瓶颈(块操作)。

id

空闲时间百分比

实例2:vmstat –a 显示活跃和非活跃内存,显示增加了inact和active列


示例3: vmstat -s 查看内存使用的详细信息

[root@zhaokk opt]# vmstat -s1882740 K total memory440544 K used memory400700 K active memory198592 K inactive memory1200708 K free memory7252 K buffer memory234236 K swap cache0 K total swap0 K used swap0 K free swap1445025 non-nice user cpu ticks269 nice user cpu ticks1271192 system cpu ticks185869501 idle cpu ticks32682 IO-wait cpu ticks0 IRQ cpu ticks548 softirq cpu ticks0 stolen cpu ticks2058857 pages paged in10263968 pages paged out0 pages swapped in0 pages swapped out1130998510 interrupts1746041179 CPU context switches1594017679 boot time3647164 forks

示例4: vmstat -d 查看磁盘的读/写

[root@zhaokk opt]# vmstat -ddisk- ------------reads------------ ------------writes----------- -----IO------       total merged sectors      ms  total merged sectors      ms    cur    secvda    54750   1689 4117714  628393 913459 621024 20528056 2208005      0    527

示例5: 查看/dev/sda1磁盘的读/写

[root@oracledb ~]# vmstat -p /dev/sda1 sda1          reads   read sectors  writes    requested writes666       5466          7         50
来源:https://www.cnblogs.com/xqzt/p/5448983.html

Lmbench 的安装

来源https://blog.csdn.net/askbai666888/article/details/7876087

原文:1.1.3 如何减少上下文切换

减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。

·无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一

些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。

·CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。

·使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这

样会造成大量线程都处于等待状态。

·协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换

自旋锁不适于单核cpu

自旋锁能用于中断上下文(中断屏蔽)?不能

锁总线,保证原子性

原文

1.1.4 减少上下文切换实战

这里没有实践,大概是dump线程信息,改变线程池任务量

第一步:用jstack命令dump线程信息,看看pid为3117的进程里的线程都在做什么。

sudo -u admin /opt/ifeve/java/bin/jstack 31177 > /home/tengfei.fangtf/dump17


第二步:统计所有线程分别处于什么状态,发现300多个线程处于WAITING(onobject-

monitor)状态。

[tengfei.fangtf@ifeve ~]$ grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'        | sort | uniq -c 39 RUNNABLE 21 TIMED_WAITING(onobjectmonitor) 6 TIMED_WAITING(parking) 51 TIMED_WAITING(sleeping) 305 WAITING(onobjectmonitor) 3 WAITING(parking)


第三步:打开dump文件查看处于WAITING(onobjectmonitor)的线程在做什么。发现这些线

程基本全是JBOSS的工作线程,在await。说明JBOSS线程池里线程接收到的任务太少,大量线程都闲着。

"http-0.0.0.0-7001-97" daemon prio=10 tid=0x000000004f6a8000 nid=0x555e in    Object.wait() [0x0000000052423000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker) at java.lang.Object.wait(Object.java:485) at org.apache.tomcat.util.net.AprEndpoint$Worker.await(AprEndpoint.java:1464) - locked <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker) at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1489) at java.lang.Thread.run(Thread.java:662)


第四步:减少JBOSS的工作线程数,找到JBOSS的线程池配置信息,将maxThreads降到100。

<maxThreads="250" maxHttpHeaderSize="8192" emptySessionPath="false" minSpareThreads="40" maxSpareThreads="75"     maxPostSize="512000" protocol="HTTP/1.1" enableLookups="false" redirectPort="8443" acceptCount="200" bufferSize="16384" connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI= "true">


第五步:重启JBOSS,再dump线程信息,然后统计WAITING(onobjectmonitor)的线程,发现减少了175个。WAITING的线程少了,系统上下文切换的次数就会少,因为每一次从

WAITTING到RUNNABLE都会进行一次上下文的切换。读者也可以使用vmstat命令测试一下。

[tengfei.fangtf@ifeve ~]$ grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'    | sort | uniq -c   44 RUNNABLE   22 TIMED_WAITING(onobjectmonitor)   9 TIMED_WAITING(parking)   36 TIMED_WAITING(sleeping)   130 WAITING(onobjectmonitor)1  WAITING(parking)

1.2  死锁

个人理解相当于GC算法的引用计数算法思想,互相引用,这里是互相持有,互相等待锁释放

原文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( ()->{ synchronized (A) { try { Thread.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(); }
}原文是Thread.currentThread().sleep(2000);但是我这编译不通过

运行此段代码

【此时打开了脏话开关,产品第三次变更需求降临】

使用jconsole监测死锁

原文

现在我们介绍避免死锁的几个常见方法。

·避免一个线程同时获取多个锁。

·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。

·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

今天先到这,下午还有两需求会要开,拜拜,心情不美丽


本文分享自微信公众号 - 赵KK日常技术记录(gh_cc4c9f1a9521)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!