Java多线程学习笔记
2016-09-02 17:01
190 查看
在Java开发和Android开发过程中,我们不可避免的会涉及到线程操作,多线程的情况也经常会遇到。此外,多线程问题一直都是Android工程师和Java工程师面试过程中出现频率比较高的几个问题之一,所以非常有必要对这部分有一个较为深刻的理解。
进程:一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。
线程:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
资源分配给进程,同一进程的所有线程共享该进程的所有资源。
处理机分给线程,即真正在处理机上运行的是线程。
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
线程是进程的一部分,CPU调度的是线程,系统为进程分配资源,不对线程分配资源。
用Thread实现
用Runnable实现
注意:创建一个新的线程必须重写run()方法,我们只能通过Thread.start()方法来开启新的线程,run()方法可以直接被调用,此时该方法运行在当前线程中,并不会开启一个新线程。
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法t.start(),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又
acca
可以分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
各个状态的切换如下图所示:
![](http://i.imgur.com/XEP019c.png)
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait(),放弃对象锁.之后在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足,就可以唤醒线程A:
注意:
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内;
调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {…} 代码段内唤醒A。
当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。
http://blog.jobbole.com/76308/
http://www.cnblogs.com/lwbqqyumidi/p/3804883.html
http://blog.csdn.net/luoweifu/article/details/46613015
http://blog.csdn.net/luoweifu/article/details/46664809
http://www.cnblogs.com/bastard/archive/2012/09/05/2672046.html
进程和线程的区别
在进行多线程的学习之前,我们需要搞清楚进程和线程的区别和联系。进程:一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。
线程:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
进程和线程的关系
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。资源分配给进程,同一进程的所有线程共享该进程的所有资源。
处理机分给线程,即真正在处理机上运行的是线程。
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
进程与线程的区别
调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
线程是进程的一部分,CPU调度的是线程,系统为进程分配资源,不对线程分配资源。
如何创建一个线程
在Java中,有两种方法可以创建一个新的线程,第一种是继承Thread类;第二种是实现Runnable接口。用Thread实现
public class ThreadDemo extends Thread{ public void run() { // do something... super.run(); } public staticvoid main(String[] args) { ThreadDemo t = new ThreadDemo(); t.start(); } }
用Runnable实现
public class ThreadDemo implements Runnable{ public void run() { // do something... } public static void main(String[] args) { ThreadDemo t = new ThreadDemo(); new Thread(t).start(); } }
注意:创建一个新的线程必须重写run()方法,我们只能通过Thread.start()方法来开启新的线程,run()方法可以直接被调用,此时该方法运行在当前线程中,并不会开启一个新线程。
线程的几种状态
线程一共有5种状态,如下所示:新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法t.start(),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又
acca
可以分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
各个状态的切换如下图所示:
![](http://i.imgur.com/XEP019c.png)
线程同步
线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步机制,保证了该变量的唯一性和准确性。同步有以下几种实现方法:synchronized,wait和notify。synchronized
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
wait notify notifyAll
在Java的Object类中有三个final的方法允许线程之间进行资源对象锁的通信,他们分别是: wait(), notify() and notifyAll()。调用这些方法的当前线程必须拥有此对象监视器(即锁),否则将会报java.lang.IllegalMonitorStateException exception异常。wait
Object的wait方法有三个重载方法,其中一个方法wait() 是无限期(一直)等待,直到其它线程调用notify或notifyAll方法唤醒当前的线程;另外两个方法wait(long timeout) 和wait(long timeout, int nanos)允许传入 当前线程在被唤醒之前需要等待的时间,timeout为毫秒数,nanos为纳秒数。notify
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll
notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。这些方法可以使用于“生产者-消费者”问题,消费者是在队列中等待对象的线程,生产者是在队列中释放对象并通知其他线程的线程。wait、notify、notifyall、synchronized的使用机制
synchronized(obj) { while(!condition) { obj.wait(); } obj.doSomething(); }
当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait(),放弃对象锁.之后在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足,就可以唤醒线程A:
synchronized(obj) { condition = true; obj.notify(); }
注意:
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内;
调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {…} 代码段内唤醒A。
当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。
总结
通过结合多个博客进行学习,基本上对Java的多线程问题有了更深刻的理解。多线程问题是一个比较重要的问题,搞清楚它会使得我们之后的工作事半功倍。参考
http://www.3lian.com/edu/2015/07-28/233571.htmlhttp://blog.jobbole.com/76308/
http://www.cnblogs.com/lwbqqyumidi/p/3804883.html
http://blog.csdn.net/luoweifu/article/details/46613015
http://blog.csdn.net/luoweifu/article/details/46664809
http://www.cnblogs.com/bastard/archive/2012/09/05/2672046.html
相关文章推荐
- java多线程学习笔记3
- JAVA多线程学习笔记
- 学习笔记 java多线程 信号量(Semaphore),死锁
- Java学习笔记(多线程)
- Java多线程学习笔记
- JAVA学习笔记之多线程
- 学习java多线程的笔记4--传智播客_张孝祥_空中网挑选实习生的面试题(来源于视频)
- JAVA多线程学习笔记—1
- 学习笔记 java多线程(四)线程间协作
- JAVA学习笔记————多线程
- [零散篇]Java学习笔记---Java的Socket网络编程以及多线程
- 学习java多线程的笔记1--Thread(Runnable t)与重写run()方法等
- Java学习笔记---多线程
- Java学习笔记9——多线程
- Java多线程学习笔记1
- Java 多线程编程 学习笔记
- 黑马程序员java学习笔记之四(java多线程总结)
- 黑马程序员---java多线程 学习笔记
- 学习笔记 - java.util.concurrent 多线程框架(引)
- java学习笔记1017---多线程