Java并发编程艺术系列-一、并发编程问题与解决

一、并发编程问题与解决

  • 上下文切换
  • 死锁
  • 资源限制

1.1 上下文切换

1.1.1 问题

  • CPU通过时间片分配算法来循环执行任务,当前任务一个时间片执行完后会切换到下一个 任务,要保存上一个任务的状态,有一定的开销
  • 多线程不一定快 - 因为上下文切换的开销

1.1.2 解决

  • 无锁并发编程:根据id将数据hash分配到不同线程来避免使用锁
  • CAS算法:自旋锁(注意:自旋CPU消耗大)
  • 最少线程:避免创建不需要的线程,线程越多,上下文就多,等待线程也多,切换也频繁
  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

1.1.3 解决实战

  • 工具

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

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

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

      $ vmstat 1 procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu----- 
      r b swpd free buff cache si so bi bo in cs us sy id wa st 
      0 0 0 127876 398928 2297092 0 0 0 4 2 2 0 0 99 0 0 
      0 0 0 127868 398928 2297092 0 0 0 0 595 1171 0 1 99 0 0 
      0 0 0 127868 398928 2297092 0 0 0 0 590 1180 1 0 100 0 0 
      0 0 0 127868 398928 2297092 0 0 0 0 567 1135 0 1 99 0 0
      

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

  • 1.jstack命令dump线程信息

    jstack 35031 > dump31
    
  • 2.统计线程状态

    grep java.lang.Thread.State dump31 | 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)
    
  • 3.打开dump文件查看WAITING(onobjectmonitor)发现是jboss线程,接收任务太少,大量线程闲着waitting

  • 4.配置jboss使用较少线程

  • 5.重新统计发现waitting少了,waitting线程少了,上下文切换就少,因为每一次从 WAITTING到RUNNABLE都会进行一次上下文的切换。可以使用vmstat命令验证一下

1.2 死锁

1.2.1 问题

  • 线程t1和线程t2互相等待对方释放锁。

1.2.2 解决

  • dump线程查看出现问题的线程 java.lang.Thread.State: BLOCKED

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

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

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

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

1.3 资源限制

1.3.1 问题

  • 硬件资源限制:网络带宽、硬盘读写、CPU处理速度
  • 软件资源限制:连接数(数据库,socket)
  • 受限于资源,多线程程序依然在串行执行,因为增加了切换上下文和资源调度的开销,反而效率更低

1.3.1 解决

  • 硬件资源:多机部署解决
  • 软件资源:连接池、io多路复用
  • 根据资源限制调整并发配置。
上一篇:Java线程生命周期及常用方法说明


下一篇:解决 Linux : time out waiting for input: auto-logout问题