01并发编程的挑战

上下文切换

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

如何减少上下文切换

  • 减少上下文切换的方法有无锁并发编程,CAS算法,使用最少线程和协程。
  • 无锁并发编程:多线程竞争锁时,会引起上下文切换,可以用一些办法来避免使用锁,比如将数据的ID按照Hash算法取模分段,不同的线程处理不同的数据。
  • CAS算法:java的Atomi包使用CAS算法来更新数据,而不需要加锁。
  • 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程处理,这样会造成大量的线程等待状态。
  • 协程:在单线程里实现多任务调度,并在单线程里维持多个任务间的切换。

jstack 工具

jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项”-J-d64”,Windows的jstack使用方式只支持以下的这种方式:

jstack [-l] pid

主要分为两个功能:

a. 针对活着的进程做本地的或远程的线程dump;

b. 针对core文件做线程dump。

jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

jstack命令主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)。

线程状态

想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:

  • NEW,未启动的。不会出现在Dump中。
  • RUNNABLE,在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
  • BLOCKED,受阻塞并等待监视器锁。被某个锁(synchronizers)給block住了。
  • WATING,无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
  • TIMED_WATING,有时限的等待另一个线程的特定操作。和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。
  • TERMINATED,已退出的。

http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html

线程状态为“waiting for monitor entry”
意味着它 在等待进入一个临界区 ,所以它在”Entry Set“队列中等待。
此时线程状态一般都是 Blocked:
java.lang.Thread.State: BLOCKED (on object monitor)
线程状态为“waiting on condition”
说明它在等待另一个条件的发生,来把自己唤醒,或者干脆它是调用了 sleep(N)。
此时线程状态大致为以下几种:
java.lang.Thread.State: WAITING (parking):一直等那个条件发生;
java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己。
如果大量线程在“waiting for monitor entry”
可能是一个全局锁阻塞住了大量线程。
 如果短时间内打印的 thread dump 文件反映,随着时间流逝,waiting for monitor entry 的线程越来越多,没有减少的趋势,可能意味着某些线程在临界区里呆的时间太长了,以至于越来越多新线程迟迟无法进入临界区。
如果大量线程在“waiting on condition”
可能是它们又跑去获取第三方资源,尤其是第三方网络资源,迟迟获取不到Response,导致大量线程进入等待状态。
所以如果你发现有大量的线程都处在 Wait on condition,从线程堆栈看,正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行
线程状态为“in Object.wait()”:
说明它获得了监视器之后,又调用了 java.lang.Object.wait() 方法。
每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”。
当线程获得了 Monitor,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。
此时线程状态大致为以下几种:
java.lang.Thread.State: TIMED_WAITING (on object monitor);
java.lang.Thread.State: WAITING (on object monitor);

怎样统计java应用线程状态

第一步: 使用jps 查看进程ID

[gzpflm@web1 ~]$ jps
59133 Jps
91722 Bootstrap
35139 Bootstrap
[gzpflm@web1 ~]$ ^C

第二步:用jstack命令dump线程信息

[gzpflm@web1 ~]$ jstack 91722 > threadDump.txt

第三步:统计所有线程分别处于什么状态

[gzpflm@web1 ~]$ grep java.lang.Thread.State threadDump.txt | awk '{print $2$3$4$5}' | sort | uniq -c
     11 RUNNABLE
     14 TIMED_WAITING(onobjectmonitor)
      2 TIMED_WAITING(parking)
      2 TIMED_WAITING(sleeping)
      3 WAITING(onobjectmonitor)
     26 WAITING(parking)
[gzpflm@web1 ~]$ 

命令解析:
- | 是管道操作符 ,把前一个命令的输出做为后一个命令的输入;
- grep 命令在文件threadDump.txt中找出含有java.lang.Thread.State的行;
- awk从grep命令的输出中找出第2,3,4,5列的内容(默认用空格分割列);
- sort命令对awk的输出按首字母进行排序;
- uniq -c 对sort的输出进行相同行统计。

死锁

/**
 * 死锁演示
 * @author qxw
 * 2018年4月26日
 */
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() {
            @SuppressWarnings("static-access")
            @Override
            public void run() {
                synchronized (A) {
                    try {
                        Thread.currentThread().sleep(2000);
                    } catch (Exception 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();
    }
}

避免死锁的几个常见方法

  • 避免一个线程同时获取多个锁
  • 避免一个线程在锁内同时占用多个资源,尽量保证每一个锁只占用一个资源
  • 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会会出现解锁失败的情况。

资源限制的挑战

什么是资源限制

资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源。比如服务器的带宽,软件资源限制有数据库链接数和socket连接数。

资源限制引发的问题

串行执行的代码改成并发执行,但因为受限于资源限制,仍然在串行执行,这时因为增加了上下文切换和资源调度的时间,反而执行更慢

如何解决资源限制

对于硬件的资源限制,可以考虑使用集群并发执行程序。既然单机的资源有限制,那吗就让程序在多机上运行。
对于软件资源限制,可以考虑将资源池复用。比如使用连接池将数据库和Socket链接复用,或者调用对方webservice接口获取数据时,只建立一个链接。

如何在资源限制情况下进行并发编程

根据不同的资源限制调整程序的并发度,比如下载文件程序依赖于两个资源(带宽和硬盘读写速度)。有数据库操作时,涉及数据库连接数,如果SQL语句执行非常快,而线程的数量比数据库连接数大很多,则某些线程会被阻塞,等待数据库链接。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值