随着整个社会的不断发展,软件已经成文人们日常生活中不可或缺对一部分。随之而来的是计算机技术的不断发展和人们日益增长的对于软件的要求,例如,为了提高计算机的处理能力,以冯·诺伊曼体系为基础的计算机不断发展为多核/多处理的计算机;人们对软件的友好性/数据处理能力等的要求不断提示。
Java作为一门撑起互联网及软件业半边天的编程语言自然也要适应不断变化的世界,Java并发多线程技术在某种程度上算是这一背景下的产物。
Java并发简介
Java并发模型是一种旨在提高程序处理速度/优化编程模型的技术,它的底层依托于计算机的进程与线程模型。
并发编程的好处
- 提高数据处理效率
现代计算机都已搭载多核多cpu,多线程模型可有效利用这些计算资源,使软件在同一时刻可以并行处理数据,进而提高数据处理的效率; - 创建可响应的程序
用一个独立的线程来响应一次具体的用户请求,而不是对在单线程中集中处理,这可以利用计算机的计算资源,更快的给用户返回处理结果; - 优化编程模型
多线程可以有效解决仿真类问题,用每个线程模拟一个实体,并通过线程间通信让这些实体运转起来。传统的顺序编程模型很难解决此类仿真问题。
并发编程的挑战
上下文切换带来的开销
在多线程环境下,由于cpu在轮询时间片使需要记录每个线程上次的执行位置,进而在多线程环境下会引入额外的线程上下文切换开销,所以并不是程序的并行度越高,程序的运行效率就越高
package com.learn.concurrency.test;
public class ConcurrencyTest {
private static long count = 1000000000;
public static void main(String[] args) throws Exception {
concurrency();
serial();
}
//并行
public static void concurrency() throws Exception{
long start = System.currentTimeMillis();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for(int i = 0;i < count;i ++) {
a += 5;
}
}
});
thread.start();
int b = 0;
for (int j = 0;j < count;j ++) {
b --;
}
thread.join();
long end = System.currentTimeMillis();
System.out.println("concurrency:" + (end - start) + "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(int j = 0;j < count;j ++) {
b --;
}
long end = System.currentTimeMillis();
System.out.println("serial:" + (end - start) + "ms, a = " + a + ", b = " + b);
}
}
该测试摘自《并发编程的艺术》,count取值不通,并行和串行的在效率上的表现也不相同:
count | 并行 | 串行 |
---|---|---|
10000 | 2ms | 1ms |
100000 | 6ms | 6ms |
1000000 | 7ms | 8ms |
由结果可以看出,当上下文切换的开销小于计算开销时,多线程带来的效率提示就可以抵消上下文切换带来的额外开销。
减少上下文切换开销的常见方式:
- 无锁并发编程,例如Hash分区计算
- CAS算法
- 使用最少的线程数
死锁
在多线程环境中,由于需要保证共享资源对线程安全,会引入锁机制,这就存在发生死锁但风险。
public class DeadLockTest {
static final String lockA = "A";
static final String lockB = "B";
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (lockA) {
TimeUnit.SECONDS.sleep(2);
synchronized (lockB) {
System.out.println("lockA -> lockB");
}
}
}catch (Exception ex) {}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (lockB) {
TimeUnit.SECONDS.sleep(2);
synchronized (lockA) {
System.out.println("lockB -> lockA");
}
}
}catch (Exception ex) {}
}
});
thread1.start();
thread2.start();
}
}
当线程之间互相等待释放锁时,就会出现死锁,导致整个系统停止服务。
在编写并发程序时,需要注意一下几点:
- 避免一个线程或许多个锁
- 避免一个线程在锁内占有多个共享资源
- 用超时锁(tryLock(time))代替synchronized
共享资源的限制
共享资源包括IO/网络/数据块链接等,当这些资源程序系统瓶颈时,多线程并不能解决问题。。