您的位置:首页 > 编程语言 > Java开发

Java多线程学习笔记

2016-09-02 17:01 190 查看
在Java开发和Android开发过程中,我们不可避免的会涉及到线程操作,多线程的情况也经常会遇到。此外,多线程问题一直都是Android工程师和Java工程师面试过程中出现频率比较高的几个问题之一,所以非常有必要对这部分有一个较为深刻的理解。

进程和线程的区别

在进行多线程的学习之前,我们需要搞清楚进程和线程的区别和联系。

进程:一个进程是一个独立(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()方法,该线程结束生命周期。

各个状态的切换如下图所示:



线程同步

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。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.html

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: