理解高并发(一)

前言:对于并发编程,涉及的知识点非常多, 我们首先需要明确一些基本概念,只有概念清晰,才能做到在以后深入学习关键技术的过程中不致于感觉到吃力和迷惑。

并发编程需要注意的两个问题
并发编程尤其需要注意的共享数据的安全性方面和锁性能方面的问题,在并发编程领域的讨论中,几乎百分之九十以上都是围绕这两大主题展开的。甚至JDK每个版本的升级都有针对这两方面问题做的优化,如JDK5之后的各种锁优化技术,volatile,threadlocal关键字等。

1)共享数据的安全性问题
堆内存和方法区内存可以共享,因此成员变量和静态变量存在数据安全性问题。
2)锁竞争带来的程序效率问题
多个线程访问共享数据资源时,只有获取到锁的线程才允许访问共享资源,未得到锁的线程只有在临界区进入排队等待,试想如果有1000个线程同时访问共享资源,那么最后一个线程必须要等待前面999个线程执行完后才能进入临界区操作(争抢临界资源的实质:对同一把锁的争抢)共享资源。如果锁没有控制好,非常容易程序整体性能低下的情况。

高并发:高并发是请求,指的是多个客户端同一时刻向服务端发送请求,它是一种现象。比如,电商网在某一时刻同时有1K个下单请求。

多线程:多线程是处理,指的是多个执行者处理同一类的任务,它有具体的实现,比如:电商网在某一时刻同时有100个线程处理1K个下单请求。

并行:多核CPU的情况,多个任务执行者并行处理任务。

并发:单个CPU的情况下,CPU间断性的执行多个任务。

原子性:不可分,从头执行到尾,不能被其他线程同时执行。

多线程一定高效吗?:多线程并非效率一定高,只有在并行的庆康下效率才能保证,并发需要做到上下文切换,会影响整个性能。

线程池:线程池只要为了节约创建线程的开销、复用已经创建的线程,用的时候一般都是全局new 一个或者你有特殊需求new 一个。
固定线程数量为5的线程池,不管几个runnable,Callable过来,同时跑的线程只有5个,其他的线程都得排队。(最佳线程数取决于你要处理的任务类型和你的cpu数量)

同步和异步:这个是多线程的概念。异步就是执行的时候,他们的时间片有交叉。异步就是我执行的时候,你还要执行。同步就是我执行完了,你再执行。

哪些情况建议使用同步交互?
比如银行的转账系统,对数据的保存操作等等都会使用同步交互操作,其余情况都优先使用异步交互。

脏数据:脏数据就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据,因为这个数据是还没有提交到的数据,那么另外一个事务读到的这个数据就是脏数据,依据脏数据所做的操作可能是不正确的。

不可重复读:不可重复读是指在一个事务内,多次读同一数据,在这个事务还没有结束时,另外一个事务也访问该同一数据,那么,在第一个事务中的两次读数据之间,由于第二事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了一个事务内两次读到的数据是不一样的,因此是不可重复读。

java中的锁:是用来控制多个线程访问共享资源的方式(即锁能防止多个线程同时访问资源而出现的线程安全问题)。在实践过程中使用最常见的锁就是synchronized,在jdk1.5之前也仅仅只有这一种锁而已。在jdk1.5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁的功能。Lock接口提供了synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。虽然Lock接口没有synchronized关键字自动获取和释放那么方便,但Lock接口却具有锁的可操作性性,可中断获取以及超时获取锁等多种非常使用的同步特性,除此之外,Lock接口还有两个非常强大的实现类重入锁和读写锁~

重入锁与不可重入锁
当一个线程获取到当前实例的锁Lock,并且进入方法A,该线程在方法A没有释放该锁的时候,是否可以再次进入使用该锁的方法B?

不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待,实现简单

可重入锁:不仅判断这个锁有没有被锁上,还会判断锁是谁上的,当就是自己锁上的时候,那么它依旧可以再次访问临界资源,并且锁次数+1
设计了加锁次数,在解锁的时候,要确保所有加锁的过程都解锁了,其他线程才能访问,不然没有加锁的参考值,也就不知道什么时候解锁,解锁多少次,才能保证本线程已经访问完临界资源了,可以唤醒其他线程访问了,实现相对复杂。

对不可重入锁的理解:
Public class Test{
Lock lock=new Lock();
public void methodA(){
lock.lock();

methodB();

lock.unlock();
}
public void methodB(){
lock.lock();
------------------------;
lock.unlock();
}

}

synchronized:加入这个同步的监视对象是类的话,那么当一个对象访问类里面的同步方法,那么其他的对象如果想要继续访问类里面的这个同步方法的话,就会进入阻塞,只有等待一个对象执行完成该同步方法后,当前对象才能够继续执行该方法,这就是同步。相反,如果方法前没有同步关键字的修饰,那么不同的对象可以在同一时间访问统一方法,这就是异步。

sychronize特性
1)同一时刻只有一个线程访问临界资源
2)其他未获取到锁执行权的线程必须排队等待
3)保证共享资源对的原子性,可见性和有序性
4)进入synchronized范围内自动加锁,synchronized作用外锁自动消除,异常情况也会释放锁。
5)简单方便(不需要人工释放锁)
6)JDK1.6后性能做了很大优化

synchronized用法
1)普通方法同步
作用:所有访问该对象、实例该方法的线程,必须排队执行
如:synchronized public void add(){}

2)静态方法同步
作用:所有采用类名、方法名访问的线程,必须排队执行
如:synchronized public static void add(){}

3)代码块同步
作用:所有访问到该表达式的线程,必须排队执行
如:synchronized(表达式){}

小科普:
临界资源对象:代表多个线程都能找到的、多个线程都能访问到的对象
synchronized锁什么?
锁对象,可能锁的对象包括:this.临界资源对象,class对象

sychronized与Lock
在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock。二者并没有什么必然联系,且各有各的特点,在使用中可以进行取舍使用。

实现:首先最大的不同:synchronized是基于JVM层面实现的,而Lock是基于JK层面实现的。synchronized的实现难找,但Lock却是基于JDK实现的,可以通过阅读JDK的源码来理解Lock的实现。

使用感受:使用上的直观感受是Lock比较复杂,需要lock和realse,如果忘记释放锁就会产生死锁的问题,所以通常需要在finailly中进行锁的释放,但是synchronized的使用十分简单,只需要对自己的方法或者关注额同步对象或类使用synchronized关键字修饰即可,但是对于锁的粒度控制比较粗,同时对实现一些锁的状态的转移比较困难。

死锁:进程A占有资源R1,等待进程B占有的资源R2,进程B占有资源R2,等待进程A占有的资源R1,而且资源R1和R2只允许一个进程,即不允许两个进程同时占用。结果两个进程都不能继续执行,若不采取其他措施,这种循环等待状态会无限期的持续下去,就发生了进程死锁。在计算机系统中,涉及软件,硬件资源都可能发生死锁。例如系统中只有一台CD-ROM驱动器和一台打印机,某一个进程占有了CD-ROM驱动器,又申请了打印机;另一个进程占有打印机,还申请CD-ROM。结果两个进程都被阻塞,永远不能自动解除。

你可能感兴趣的:(理解高并发(一))