多线程详解

多线程是java应用程序的一个特色,掌握java的多线程也是做为一 java程序员必备的知识。多线程指的是在单个程序中能够同时运行多个 同的线程执行不一样的任务.线程是程序内的顺序控制流,只能使用分配给 序的资源和环境。还记得刚开始学习的时候老是和进程分不清,老是对 这两个名词所迷惑。

下面就首先对这两个名词区分来做为本篇博客的开始: java


    1、线程与进程的区别 程序员


多个进程的内部数据和状态都是彻底独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响. •线程自己的数据一般只有寄存器数据,以及一个程序执行时使用的堆栈,因此线程的切换比进程切换的负担要小。 编程


 多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不须要占用CPU而只和I/O等资源打交道时,让须要占用CPU资源的其它线程有机会得到CPU资源。从根本上说,这就是多线程编程的最终目的。 数组


2、了解一下java在多线程中的基础知识 多线程


1.Java中若是咱们本身没有产生线程,那么系统就会给咱们产生一个线程(主线程,main方法就在主线程上运行),咱们的程序都是由线程来执行的。 并发


 2. 进程:执行中的程序(程序是静态的概念,进程是动态的概念)。  post


3. 线程的实现有两种方式,第一种方式是继承Thread类,而后重写run方法;第二种是实现Runnable接口,而后实现其run方法。  学习


4. 将咱们但愿线程执行的代码放到run方法中,而后经过start方法来启动线程,start方法首先为线程的执行准备好系统资源,而后再去调用run方法。当某个类继承了Thread类以后,该类就叫作一个线程类。  spa


5. 一个进程至少要包含一个线程。  .net


6. 对于单核CPU来讲,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行)。 


7. 对于双核或双核以上的CPU来讲,能够真正作到微观并行。


3、Thread源码研究: 


1) Thread类也实现了Runnable接口,所以实现了Runnable接口中的run方法; 


2) 当生成一个线程对象时,若是没有为其设定名字,那么线程对象的名字将使用以下形式:Thread-number,该number将是自动增长的,并被全部的Thread对象所共享(由于它是static的成员变量)。 


3) 当使用第一种方式来生成线程对象时,咱们须要重写run方法,由于Thread类的run方法此时什么事情也不作。


4)当使用第二种方式生成线程对象时,咱们须要实现Runnable接口的run方法,而后使用new Thread(new MyThread())(假如MyThread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法或调就会MyThread类的run方法,这样咱们本身编写的run方法就执行了。


说明: 


Public void run(){


If(target!=null){


Target.run();


}}


 


当使用继承Thread生成线程对象时,target为空,什么也不执行,当使用第二种方式生成时,执行target.run(),target为runnable的实例对象,即为执行重写后的方法


总结:两种生成线程对象的区别:


1.两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。 


2.在具体应用中,采用哪一种方法来构造线程体要视状况而定。一般,当一个线程已继承了另外一个类时,就应该用第二种方法来构造,即实现Runnable接口。 


四:线程的生命周期:



由上图能够看出,一个线程由出生到死亡分为五个阶段:


1).建立状态 


•当用new操做符建立一个新的线程对象时,该线程处于建立状态。 


•处于建立状态的线程只是一个空的线程对象,系统不为它分配资源 


2). 可运行状态 


•执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体—run()方法,这样就使得该线程处于可运行( Runnable )状态。 


•这一状态并非运行中状态(Running ),由于线程也许实际上并未真正运行。 


3).不可运行状态 


.当发生下列事件时,处于运行状态的线程会转入到不可运行状态。 


调用了sleep()方法; 


•线程调用wait方法等待特定条件的知足 


•线程输入/输出阻塞 


4返回可运行状态: 


•处于睡眠状态的线程在指定的时间过去后 


•若是线程在等待某一条件,另外一个对象必须经过notify()notifyAll()方法通知等待线程条件的改变 


•若是线程是由于输入/输出阻塞,等待输入/输出完成 


5. 消亡状态 


当线程的run方法执行结束后,该线程天然消亡。 


注意:


1.中止线程的方式:不能使用Thread类的stop方法来终止线程的执行。通常要设定一个变量,在run方法中是一个循环,循环每次检查该变量,若是知足条件则继续执行,不然跳出循环,线程结束。 


2.不能依靠线程的优先级来决定线程的执行顺序。 


五:多线程并发


多线程并发是线程同步中比较常见的现象,java多线程为了不多线程并发解决多线程共享数据同步问题提供了synchronized关键字


synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫作同步方法。 


1.Java中的每一个对象都有一个锁(lock)或者叫作监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其余任何线程都没法再去访问该synchronized方法了,直到以前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其余线程才有可能再去访问该synchronized方法。 


2. 若是一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其余线程是没法访问该对象的任何synchronized方法的。 


 


3.若是某个synchronized方法是static的,那么当线程访问该方法时,它锁的并非synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象由于Java中不管一个类有多少个对象,这些对象会对应惟一一个Class对象,所以当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另外一个线程才开始执行。 


4. synchronized块,写法: 


synchronized(object) 




表示线程在执行的时候会对object对象上锁。 


5.synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块以外的代码是能够被多个线程同时访问到的。 


同步的线程状态图:



六:wait与notify


1.wait与notify方法都是定义在Object类中,并且是final的,所以会被全部的Java类所继承而且没法重写。这两个方法要求在调用时线程应该已经得到了对象的锁,所以对这两个方法的调用须要放在synchronized方法或块当中。当线程执行了wait方法时,它会释放掉对象的锁。 


2. 另外一个会致使线程暂停的方法就是Thread类的sleep方法,它会致使线程睡眠指定的毫秒数,但线程在睡眠的过程当中是不会释放掉对象的锁的。 


3.notify():唤醒在此对象监视器上等待的单个线程。若是全部线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现作出决定时发生。线程经过调用其中一个 wait 方法,在对象的监视器上等待。 


直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其余全部线程进行竞争;例如,唤醒的线程在做为锁定此对象的下一个线程方面没有可靠的特权或劣势。


此方法只应由做为此对象监视器的全部者的线程来调用。经过如下三种方法之一,线程能够成为此对象监视器的全部者:


o 经过执行此对象的同步实例方法。


o 经过执行在此对象上进行同步的 synchronized 语句的正文。


o 对于 Class 类型的对象,能够经过执行该类的同步静态方法。


一次只能有一个线程拥有对象的监视器。



 关于成员变量与局部变量:若是一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操做时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另外一个线程)。  若是一个变量是局部变量,那么每一个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其余的线程。


七:死锁的问题: 


定义:线程1锁住了对象A的监视器,等待对象B的监视器,线程2锁住了对象B的监视器,等待对象A的监视器,就形成了死锁。


     致使死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的做用是,确保在某个时刻只有一个线程被容许执行特定的代码块,所以,被容许执行的线程首先必须拥有对变量或对象的排他性访问权。当线程访问对象时,线程会给对象加锁


Java中每一个对象都有一把锁与之对应。但Java不提供单独的lockunlock操做。下面笔者分析死锁的两个过程上锁锁死” 


(1) 上锁
     许多线程在执行中必须考虑与其余线程之间共享数据或协调执行状态,就须要同步机制。所以大多数应用程序要求线程互相通讯来同步它们的动做,在 Java 程序中最简单实现同步的方法就是上锁。在 Java 编程中,全部的对象都有锁。线程可使用 synchronized 关键字来得到锁。在任一时刻对于给定的类的实例,方法或同步的代码块只能被一个线程执行。这是由于代码在执行以前要求得到对象的锁。


    为了防止同时访问共享资源,线程在使用资源的先后能够给该资源上锁和开锁。给共享变量上锁就使得 Java 线程可以快速方便地通讯和同步。某个线程若给一个对象上了锁,就能够知道没有其余线程可以访问该对象。即便在抢占式模型中,其余线程也不可以访问此对象,直到上锁的线程被唤醒、完成工做并开锁。那些试图访问一个上锁对象的线程一般会进入睡眠状态,直到上锁的线程开锁。一旦锁被打开,这些睡眠进程就会被唤醒并移到准备就绪队列中。


(2)锁死
     若是程序中有几个竞争资源的并发线程,那么保证均衡是很重要的。系统均衡是指每一个线程在执行过程当中都能充分访问有限的资源,系统中没有饿死和死锁的线程。当多个并发的线程分别试图同时占有两个锁时,会出现加锁冲突的情形。若是一个线程占有了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。


    在编写多线程代码时,笔者认为死锁是最难处理的问题之一。由于死锁可能在最意想不到的地方发生,因此查找和修正它既费时又费力。例如,常见的例子以下面这段程序。print?


1 public int sumArrays(int[] a1, int[] a2){  


2   int value = 0;  


3   int size = a1.length;  


4   if (size == a2.length) {  


5      synchronized(a1) { //1        


6        synchronized(a2) { //2          


7          for (int i=0; i


8             value += a1[i] + a2[i];  


9        }    


10      }    


11   } return value;  


12 }   


这段代码在求和操做中访问两个数组对象以前锁定了这两个数组对象。它形式简短,编写也适合所要执行的任务;但不幸的是,它有一个潜在的问题。这个问题就是它埋下了死锁的种子。


ThreadLocal类(这个类本人没用过,占时不太懂)

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,通常状况下,经过ThreadLocal.set() 到线程中的对象是该线程本身使用的对象,其余线程是不须要访问的,也访问不到的。各个线程中访问的是不一样的对象。

另外,说ThreadLocal使得各线程可以保持各自独立的一个对象,并非经过ThreadLocal.set()来实现的,而是经过每一个线程中的new 对象 的操做来建立的对象,每一个线程建立一个,不是什么对象的拷贝或副本。经过ThreadLocal.set()将这个新建立的对象的引用保存到各线程的本身的一个map中,每一个线程都有这样一个map,执行ThreadLocal.get()时,各线程从本身的map中取出放进去的对象,所以取出来的是各自本身线程中的对象,ThreadLocal实例是做为mapkey来使用的。

若是ThreadLocal.set()进去的东西原本就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的仍是这个共享对象自己,仍是有并发访问问题
相关文章
相关标签/搜索