一,Java并发简史
在某些情况下,程序必须等待某个外部操作执行完成,例如输入操作或输出操作等,而在等待时程序无法执行其它任何工作.因此,如果在等待的同时可以运行另一个程序,那么无疑提高了资源的利用率.
不同的用户和程序对计算机资源有着同等的使用权.一种高效的运行方式是通过粗粒度的时间片使这些用户和程序能共享计算机资源,而不是一个程序从头运行到尾,然后在启动下一个程序.
通常来说,在计算机多任务时,应该编写多个程序,每个程序执行一个任务并在必要时相互通信,这比只编写一个程序来计算所有的任务更容易实现.
二,Java并发的优势
- 发挥多核处理器的强大处理能力
- 建模的简单性
- 异步事件的简单化处理
- 响应更灵敏的用户界面
三,Java并发的风险
线程安全性可能是非常复杂的,在没有充足的同步情况下,多线程中的执行操作顺序是不可测试,会产生奇怪的问题.
如下代码实例 : UnsafeSequence.java ,1000个线程,并发10次,value++
public class UnsafeSequence {
/** 默认值0 */
private static int value = 0;
/** 线程执行数量 */
private static int threadCount = 1000;
/** 并发数量 */
private static int concurrentCount = 10;
/** 线程池 */
private static ExecutorService executor = Executors.newFixedThreadPool(threadCount);
/** 信号量(并发数) */
private static Semaphore semaphore = new Semaphore(concurrentCount);
/** 和join方法类似 */
private static CountDownLatch countDownLatch = new CountDownLatch(threadCount);
public static int getNext () {
return value++;
}
public static void main (String[] args) {
// 启动100 个线程 并发10次 去累加 value
for (int i = 0; i < threadCount; i++) {
executor.execute(() -> {
try {
// 获取一个许可证
semaphore.acquire();
getNext();
// 计数器减1
countDownLatch.countDown();
// 归还一个许可证
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
try {
// 阻塞主线程 直到计数器为0
countDownLatch.await();
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果可能是1000或者小于1000,问题在于,如果执行的时机不对,那么两个线程在调用getNext时会得到相同的值,虽然递增运算看起来是单个操作,但是实际上是三个独立的操作,读取value,将value加1,将计算结果写入value,由于多线程交替运行,因此两个线程同时执行读操作,读到了相同的值,结果写入主内存,结果就是不同的线程写入了相同的值.
一个并发应用程序能及时执行的能力称为活跃性,常见的有死锁,饥饿,活锁问题.
性能问题包括多个方面,例如服务器时间过长,响应不够灵敏,吞吐率过低,资源消耗过高,或者伸缩性较低等.