进程&线程
- 进程
每个进程都有独立的代码和数据空间(进程上下文);进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位) - 线程
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是 CPU 调度的最小单位)
PS: 线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,在创建线程时由系统分配,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。
注意:Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。
参考:http://www.cnblogs.com/DreamSea/archive/2012/01/11/JavaThread.html
创建线程方式
- 继承 Thread 类;
- 实现 Runnable 接口;
- 实现 Callable 接口(1.5)
- 线程池
- 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可以提高多个线程进行读操作的效率。
lock更灵活,可以自由定义多把锁的加锁解锁顺序(synchronized要按照先加的后解顺序)
提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。
本质上和监视器锁(即synchronized是一样的)
和Condition类的结合。
非 static synchronized 与 static synchronized 不互斥。
锁的分类
- 可重入锁
如果锁具备可重入性,则称作为可重入锁。
像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。 - 可中断锁
就是可以相应中断的锁。 - 公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
- ThreadPoolExecutor 类
线程池中的线程初始化:
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法
- 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
- 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于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