多线程以及死锁介绍

一.并发和并行是什么
并发:同一时刻,在同一个CPU同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换运行多个程序。)运行多个程序。
并行:并行,是每个cpu运行一个程序。(真正的同时)

同步和异步通常用来形容一次方法调用。
同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。
而,异步方法通常会在另外一个线程中,“真实”地执行着。
整个过程,不会阻碍调用者的工作。
线程的同步是指在一个多线程环境下,要保证数据的准确性和安全性。
并发编程中的三个概念:
1.原子性

原子性:即一个操作或者多个操作么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2.可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3.有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

二.实现线程同步的方法
1.synchronized关键字

       synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

  synchronized关键字最主要的四种使用方式:     

(1)普通同步方法(实例方法):锁是当前实例对象 ,进入同步代码前要获得当前实例的锁

(2)静态同步方法:锁是当前类的Class对象 ,进入同步代码前要获得当前类对象的锁

(3)同步方法块:锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁     

(4)修改一个类:其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。 

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

2.Lock  
因为Lock是接口所以使用时要结合它的实现类,另外在finall语句块中释放锁的目的是保证获取到锁之后,最终能够被释放。

Lock lock=new ReentrantLock();
  lock.lock();
   try{
    }finally{
    lock.unlock();
    }

3.volatile关键字
(1).volatile是变量修饰符,其修饰的变量具有可见性。
可见性就是说一旦某个线程修改了被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取的时候,可以立即获取修改之后的值。在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存。
(2). volatile可以禁止指令重排
指令重排是指编译器或者CPU为了提高程序的运行效率,可能会对输入的代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码中的执行结果是一致的,应用条件是单线程条件,对于并发多线程的场景下,指令重排会产生不确定的结果。
线程池:是一种线程的使用模式,它为了降低线程使用中频繁的创建和销毁所带来的资源消耗与代价。


三.常用的几种线程池
1. newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:

工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
2. newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
3. newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
4. newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

synchronized与Lock、volatile的区别:  

synchronized和 Lock 的区别? 

(1)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁

(2)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断

(3)通过Lock可以知道有没有成功获取锁(tryLock()方法:如果获取锁成功,则返回true),而synchronized却无法办到

(4)Lock可以提高多个线程进行读操作的效率

(5)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

synchronized 关键字和 volatile 关键字的区别?

(1)volatile关键字是线程同步的轻量级实现,所以volatile性能比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块

(2)多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞

(3)volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性

(4)volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。

         
四.死锁
死锁的概念:根据操作系统中的定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。  
死锁的四个必要条件:
(1)互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
(2)请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
(3)非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
(4)循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如何解决死锁问题?

处理死锁的思路如下:

(1)预防死锁:破坏四个必要条件中的一个或多个来预防死锁

(2)避免死锁:在资源动态分配的过程中,用某种方式防止系统进入不安全的状态。

(3)检测死锁:运行时产生死锁,及时发现思索,将程序解脱出来。

(4)解除死锁:发生死锁后,撤销进程,回收资源,分配给正在阻塞状态的进程。

预防死锁的办法:

(1)破坏请求和保持条件:1.一次性的申请所有资源。之后不在申请资源,如果不满足资源条件则得不到资源分配。2.只获得初期资源运行,之后将运行完的资源释放,请求新的资源。

(2)破坏不可抢占条件:当一个进程获得某种不可抢占资源,提出新的资源申请,若不能满足,则释放所有资源,以后需要,再次重新申请。

(3)破坏循环等待条件:对资源进行排号,按照序号递增的顺序请求资源。若进程获得序号高的资源想要获取序号低的资源,就需要先释放序号高的资源。

死锁的解除办法:

1、抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。

2、终止(撤销)进程:将一个或多个思索进程终止(撤销),直至打破循环环路,使系统从死锁状态解脱。

乐观锁:乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。        
悲观锁:就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作。


原文地址:https://www.cnblogs.com/liqinzhen/p/12800994.html