Java并发编程读书笔记-简介

简介
1.并发简史
2.线程的优势
a)发挥多线程处理器的强大能力
b)建模的简单性
c)异步事件的简化处理
d)响应更灵敏的用户界面
3.线程带来的风险
a)安全性问题
b)活跃性问题
c)性能问题
4.线程无处不在

为什么要编写并发程序?
使复杂的异步代码变得更加简单,从而极大的简化了复杂系统的开发。
充分发挥多处理器系统的强大计算能力。

1.并发简史

早期的计算机不包含操作系统,他们从头到尾执行一个程序,并且这个程序能访问计算机中的所有资源。如:


在这种裸机中很难编写和运行程序,并且每次只能运行一个程序,这对于昂贵并且稀缺的计算机资源来说也是一种浪费。

操作系统的出现使得计算机能够运行多个程序,操作系统给不同的程序都在单独的进程中运行:


操作系统为各个独立执行的进程分配各种资源,包括内存,文件句柄以及安全证书等。如果需要的话,在不同的进程之间可以通过一些粗粒度的通信机制来交互数据,包括:套接字、信号处理器、共享内存、信号量以及文件等。

进程是操作系统管理的基本单位。
资源利用率、公平性以及便利性促使着线程的出现。线程允许在同一个进程中同时存在多个程序控制流。线程会共享进程范围内的资源,如内存句柄和文件句柄,但每个线程都有各自的程序计数器,栈以及局部变量等。

线程也被称为轻量级进程。在大多数现代操作系统中,都是以线程为基本的调度单位,而不是进程。如果没有明确的协同机制,那么线程彼此独立执行。由于同一个进程中的所有线程都将共享进程的内存地址空间,因此这些线程都能访问相同的变量并在同一个堆上分配对象,这就需要实现一种比在进程间共享数据粒度更细的数据共享机制。如果没有明确的同步机制来协同共享数据的访问,那么当一个线程正在使用某个变量时,另一个线程可能同时访问这个变量,这是造成不可预测的结果。

2.线程的优势
a)降低程序的开发和维护成本
b)提升复杂应用程序的性能
c)降低代码的复杂度,使代码更容易编写、阅读和维护
d)在GUI应用程序中,线程可以提高用户界面的响应灵敏度
e)在服务端应用程序中,可以提升资源利用率以及系统吞吐量
f)线程还可以简化JVM的实现,垃圾收集器通常在一个或多个专门的线程中运行

3.发挥多处理器的强大能力
a)通过提高时钟频率来提升性能已变得越来越困难,处理器生产厂商都开始转向单个芯片上放置多个处理器核。
b)使用多线程还有助于在的按处理器系统上获得更高的吞吐率。

4.建模的简单性
a)一个复杂、异步的流程可以分解为一系列更简单的同步流程
5.对异步事件的简单处理
a)服务器应用程序,一个连接分配一个线程,并允许使用同步IO,开发容易
b)历史上,操作系统把一个进程可以创建的线程限制在相对比较少的数量上,大约几百(甚至更少)。因此,操作系统为了IO开发了一些高效的机制,比如Unix的select和poll系统调用,为了访问这些机制,Java类库对非阻塞的IO提供了一组包(java.nio)。
6.用户界面的更佳响应性
7.线程的风险
8.安全危险
a)在没有进行充分同步的情况下,多线程中各个操作的顺序是不可预测的。
b)线程安全性问题举例:
public class UnSafeSequence implements Runnable{

private int value;

/** 返回一个唯一值 **/
public int getNext(){
return value++;
}

public void run(){
System.out.println("多线程:"+getNext());
}

public static void main(String[] agrgs){
UnSafeSequence uss = new UnSafeSequence();

System.out.println("单一线程:"+uss.getNext());
System.out.println("单一线程:"+uss.getNext());
System.out.println("单一线程:"+uss.getNext());

UnSafeSequence uss1 = new UnSafeSequence();

Thread t = new Thread(uss1);
Thread t1 = new Thread(uss1);
Thread t2 = new Thread(uss1);

t.start();
t1.start();
t2.start();
}
}
输出:
单一线程:0
单一线程:1
单一线程:2
多线程:0
多线程:0
多线程:1


内存模型:


首先得明白每一个线程会在JVM中创建一个操作栈,而对象数据是在堆内存上分配的,线程想要操作堆内存中的数据,首先的将数据复制到操作栈中,然后执行完操作后由操作栈写入到堆内存上,而我们前面提过,线程间的操作是无序的,比如下面t,t1,t2线程没有顺序可言。

分析:
自增操作value++看起来是一个单一操作,实则不然,它分为3个操作:
1.读取这个值value(从堆内存中复制到栈内存中)
2.执行value = value +1
3.修改堆中的value值为最新值

针对线程t,t1,t2都有,若取值A,累加B,修改C三个操作。
那么数据最终由多少种可能?
tA->tB->tC->t1A->t1B->t1C->t2A->t2B->2tC 结果:0 1 2
tA->t1A->t1B->t1C->t2A->t2B->2tC->tB->tC 结果:0 1 0
....

造成线程安全的2个主谋:
1.在缺少同步时,编译器、硬件和运行时对线程间的执行顺序是不确定的(重排序)
2.JMM(堆<->线程栈<->高速缓存<->寄存器)

9.活跃性的危险
a)当一个活动进入某种它永远都无法再继续执行的状态时,活跃度失败就发生了。
b)顺序代码的活跃度失败通常是粗心造成的代码无限循环。
c)多线程的引入带来了更多的活跃度危险
i.死锁(deadlock)
ii.饥饿(starvation)
iii.活锁(livelock)

10.性能危险
a)服务时间、响应性、吞吐量、资源消费、可伸缩性来评估
b)线程能够带来性能收益,但是线程同时会给运行时带来一定程度的开销。
频繁的上下文切换(context switches)会给系统带来巨大的开销:保存和恢复线程执行的上下文,离开执行现场,并且cpu的时间会花费在对线程的调度而不是运行商。
c)线程共享数据的时候,他们必须使用同步机制,这个机制会限制编译器的优化,能够清空或锁定内存和高速缓存,并在共享内存的总线上创建同步通信
11.线程无处不在
a)并发并不是“可选的”、“高级的”语言特性。
b)定时器
c)远程方法调用(RMI):RMI使你能够调用在另外一个JVM上运行的对象的方法
当你使用RMI调用远程方法时,这个方法的参数会被打包(装配)成一个比特流,并且穿越整个网络到达远程的JVM,在那里他会被解包(分解)并传递给远程方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值