线程和锁

Posted 对酒当歌,人生几何?!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程和锁相关的知识,希望对你有一定的参考价值。

进程&线程

  • 进程
    每个进程都有独立的代码和数据空间(进程上下文);进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
  • 线程
    同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是 CPU 调度的最小单位)

PS: 线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,在创建线程时由系统分配,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。
注意:Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。

参考:http://www.cnblogs.com/DreamSea/archive/2012/01/11/JavaThread.html

创建线程方式

  1. 继承 Thread 类;
  2. 实现 Runnable 接口;
  3. 实现 Callable 接口(1.5)
  4. 线程池
  • start & run
    注意:start() 方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable)状态,什么时候运行是由操作系统决定的。
    用 start() 方法来启动线程,真正实现了多线程运行;run() 方法称为线程体,单独执行 run() 方法只是被当作普通方法的方式调用。

线程状态&生命周期

  • 新建(New):创建线程对象后 (not alive)
  • 就绪(Runnable):start() 方法调用后 (alive)
  • 运行(Running):run() 方法 (alive)
  • 阻塞(Blocked):线程放弃 CPU 的使用权。线程进入阻塞状态后,就不能进入排队队列。只有当引起阻塞的原因被消除后,线程才可以转入就绪状态 (alive)
  • 死亡(Dead):执行完任务(run运行结束),或抛出异常 (not alive)。(对于一个处于Dead状态的线程调用start方法,会出现一个运行时异常)
    Java 中线程状态关键字:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.

Thread 方法

  • sleep()
    暂停一段时间,阻塞状态;(可中断,不释放锁)
  • yield()
    当前线程让出cpu控制权,让别的就绪状态线程运行(切换);只让给同优先级的线程(如果没有同等优先权的线程,那么yield()方法将不会起作用) (不释放锁)
  • join()
    合并某个线程。在一个线程中调用other.join(), 将等待other执行完后才继续本线程(可中断)
  • interrupt()
    只能中断处于阻塞状态的线程;不能中断正在运行中的线程。

Object 方法

  • wait()
    等待,释放锁。(可中断)
    如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。

  • notify()
    唤醒一个正在等待该对象监视器的线程。
    如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。

  • notifyAll()
    唤醒所有正在等待该对象监视器的线程。
    如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

  • 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。

  • 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)。

  • 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。

  • JVM基于多线程,默认情况下不能保证运行时线程的时序性。

线程取得控制权的方法有三种:

  • 执行对象的某个同步实例方法。
  • 执行对象对应类的同步静态方法。
  • 执行对该对象加同步锁的同步块。

【Object 方法】参考:http://longdick.iteye.com/blog/453615

线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。

synchronized 关键字:指定某个方法的时候,锁定当前对象。

synchronized & Lock

  • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  • Lock可以提高多个线程进行读操作的效率。

参考:http://www.cnblogs.com/dolphin0520/p/3923167.html

lock更灵活,可以自由定义多把锁的加锁解锁顺序(synchronized要按照先加的后解顺序)
提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。
本质上和监视器锁(即synchronized是一样的)
和Condition类的结合。

非 static synchronized 与 static synchronized 不互斥。

锁的分类

  • 可重入锁
    如果锁具备可重入性,则称作为可重入锁。
    像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。
  • 可中断锁
    就是可以相应中断的锁。
  • 公平锁
    公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
    非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
    在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
    而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

线程池

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

  • ThreadPoolExecutor 类

线程池中的线程初始化:
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法

  1. 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
  2. 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
  3. 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
  4. 如果线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

参考:
http://www.cnblogs.com/dolphin0520/p/3932921.html
https://www.cnblogs.com/wxd0108/p/5479442.html
http://www.jianshu.com/p/40d4c7aebd66

以上是关于线程和锁的主要内容,如果未能解决你的问题,请参考以下文章

Java:多线程的同步方式和锁

Java虚拟机--线程安全和锁优化

JAVA多线程 线程池和锁的深度化

第四章 - 管程-悲观锁和锁优化

八JVM视角浅理解并发和锁

第七天学习多线程同步和锁