高级程序员需知的并发编程知识(一)
二营长的笔记 人气:0
## 并发编程简介
并发编程式Java语言的重要特性之一,当然也是最难以掌握的内容。编写可靠的并发程序是一项不小的挑战。但是,作为程序员的我们,要变得更有价值,就需要啃一些硬骨头了。因此,理解并发编程的基础理论和编程实践,让自己变得更值钱吧。
### 使用并发编程的优势
#### 1、充分利用多核CPU的处理能力
现在,多核CPU已经非常普遍了,普通的家用PC基本都双核、四核的,何况企业用的服务器了。如果程序中只有一个线程在运行,则最多也只能利用一个CPU资源啊,如果是一个四核的系统,岂不是最多只利用了25%的CPU资源吗?严重的浪费啊!
另外如果存在I/O操作的话,单线程的程序在I/O完成之前只能等着了,处理器完成处于空闲状态,这样能处理的请求数量就很低了。换成多线程就不一样了,一个线程在I/O的时候,另一个线程可以继续运行,处理请求啊,这样,吞吐量就上来了。
#### 2、方便业务建模
如果在程序中只包含一种类型的任务,那么比包含多种不同类型的任务的程序要更易于编写、错误更少,也更容易测试。如果在业务建模中,有多种类型的任务场景。我们可以使用多线程来解决,让每个线程专门负责一种类型的任务。
通过使用线程,可以将负责并且一步的工作流进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。
### 并发编程带来的风险
虽然并发编程帮助我们提高了程序的性能,同时也提高对我们程序员的要求,因为在编写并发程序的过程中,一不小心就面临着多线程带来的风险。这些风险主要是安全性问题、活跃性问题和性能问题。
#### 1、安全性问题
安全性问题可能是非常复杂的,在多线程场景中,如果没有正确地使用同步机制,会导致程序结果的不确定性,这是非常危险的。
比如我们熟知的 **count++** 问题
```java
public class UnsafeCount {
private static int count;
public int getCount(){
return count++;
}
}
```
上面的代码,在单线程环境中没有问题。但是如果是多个线程同时访问getCount方法,则不会得到期望的正确结果。
原因在于count ++ 不是CPU级别的原子指令,我们写了一条语句,但是在底层实际上包含了三个独立的操作:读取count,将count加1,将计算结果再写会主内存。而这多个线程有机会在其中任何一个操作时发生切换,这样便有可能两个线程拿到了同样的值,让后执行加1的操作。
#### 2、活跃性问题
当某个操作无法继续执行下去的时候,就会发生活跃性问题。在串行程序中,活跃性问题形式之一可能是无意中造成的无限循环。在多线程场景中,如果有线程A在等待线程B释放其持有的资源,而线程B永远都不释放该资源,那么线程A将永远地等待下去。
**多线程中的活跃性问题一般指的就是死锁、饥饿、活锁等。**
#### 3、性能问题
本来是用多线程是为了提高程序性能的,结果却产生了性能问题。性能问题包括多个方面,例如服务时间过长,响应不灵敏,吞吐量过地、资源消耗过高等。
使用多线程而产生性能问题的根本原因就是,创建线程、切换线程都是要带来某种运行时开销的。如果我们的程序在频繁的创建线程,那很快创建线程的消耗将增加,拖累程序整体性能。同时频繁的线程切换,也会产生性能问题。
## 创建线程的几种方法
在使用Java开始编写并发程序时,我们首先要知道在Java中应该如何创建线程,至少有下面的三种方法。通过线程池创建线程留到后面线程池章节单独说明。
### 实现Runnable接口
我们通过实现一个Runnable接口,将线程要执行的任务封装起来。
```java
public class MyTask implements Runnable{
public void run() {
// 要实行的任务
}
}
```
使用Thread对象启动线程
```java
public class MyTaskThread {
public static void main(String[] args) {
Thread thread = new Thread(new MyTask());
thread.start();
}
}
```
### 实现Callable接口
可以看到实现Runnable接口启动的线程是没有返回值的。而Callable接口可以实现有返回值地启动线程。
```java
public class MyCallableTask implements Callable
加载全部内容