首页 > 技术文章 > Java多线程

novalist 2017-02-14 09:20 原文

1.进程与线程

 

  进程:资源分配的基本单位,是一个程序或者服务的基本单位。我们可以说进程就是程序的执行过程,这个过程包括很多东西,

      如CPU执行时间、运行内存、数据等,而且是一个动态的过程。

  线程:轻量级的进程,共享在父进程拥有的资源下,每个线程在父进程的环境中顺序的独立的执行一个活动。
   

  总结:

  a>.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
  b>.资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。
    但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
  c>.处理机分给线程,即真正在处理机上运行的是线程。
  d>.线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
 

  本质区别:每个进程拥有自己的一套变量,而线程只是共享数据。

     多进程的意义:提高CPU的使用率。

     多线程的仪的意义:提高应用程序的效率。

 

  线程的优点:可以降低开发和维护的开销,并且能够提高复杂应用的技能。线程通过把的工作流程转化为普遍存在的顺序流程,使程序模拟人类工作和交互变得更容易。

     线程在GUI应用程序中是非常有用的,可用来改变用户接口的响应性,在服务器应用中,用于提高资源的利用率和吞吐量。如:JVM中的垃圾收集器即依赖于多线程。

      缺点:可能带来安全性问题。

 
  多线程编程的好处,总结一句话就是:合理利用CPU的空闲时间,来提高程序的性能。
  关于线程错误观点:越多会导致性能下降,并导致系统不稳定。

 

2.创建多线程的三种基本方式

  ①继承Thread类,重写run方法,自定义类的实例,调用start()方法启动线程。

  ②实现Runnable接口,重写run方法,自定义类的实例,调用start()方法启动线程。

  ③采用Excuter创建线程池。

  

  ①VS②下②的优势:

     a.单继承的局限

     b.更好的体现面向对象的分离(线程与程序的分离)

     c.适合多个程序去处理同一资源的情况

 

3.start()方法和run()方法的区别

   ①run方法仅仅是封装线程执行的代码,直接调用是普通方法。

   ②start方法是启动线程,由java去调用run()方法。

 

4.线程的两种调度模型

   ①分时调度模型:所有线程轮流使用CPU的使用全,平均分配每个线程占用cpu的时间。

   ②抢占式调度模型:优先让优先级搞得线程使用cpu,如果优先级相同,则随机选择一个,优先级高的获取cpu的时间片多一点。

   java采用的是抢占式调度模型 优先级最高为10,最低为1,多个抢占,一个运行.

   Java中的多线程是一种抢占式的机制,而不是分时机制。抢占式的机制是有多个线程处于可运行状态,但是只有一个线程在运行。 

 

5.线程的五大状态

   新建状态New:创建线程对象

   待运行Waiting:线程调用start()之后,有执行的资格,没有执行权。 

   运行Running:获得cpu的执行权(当加锁保证线程安全时会进入锁池等待获取锁,即同步阻塞)

   阻塞Blocked

     ①等待阻塞:Object的wait()方法调用后, 进入等待队列,唤醒后进入锁池,获得锁后又回到待运行状态。

     ②同步阻塞:争抢cpu使用权,没抢到手,进入锁池等待获得锁。

     ③其他阻塞:如sleep(),join(),时间结束后进入待运行状态。

   死亡Terminated:run,main执行结束,或因异常退出。

   线程终止的两大原因:当线程的run方法执行方法体中最后一条语句后,并经由return语句返回时,或者出现没有捕获的异常时,线程将终止。

 

  Java提供大量方法来支持阻塞
  sleep():不能得到CPU时间,指定时间一过,线程重新进入可执行状态
  suspend():是线程进入可执行状态,并且不能自动恢复,只有resume后才能使线程重新进入可执行状态
  resume():
  yield():使线程放弃当前CPU时间,但是不使线程阻塞(线程仍处于可执状态,随时可能再次分得CPU时间),
      调用yield的效果等价于调度程序认为该线程已执行足够的时间从而转到另一个时间
  wait():使线程进入阻塞状态,如果指定时间参数,超出指定时间线程重新进入可执行状态,否则必须对应的notify调用

 

6.wait()方法和sleep()方法:

   相同点:

    ①在多线程环境下调用可阻塞指定的秒数,并返回。

    ②两者都可以调用interrupt()方法打断线程的暂停状态

   不同点:

     ①每个对象都有一个锁来控制同步访问,Stnchronizad关键字可以和对象的锁交互,来实现线程的同步。

       sleep()方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

     ②wait。notify,notifyAll只能在同步方法或者同步控制块中使用,而sleep方法可以在任何地方使用

     ③sleep必须捕获异常,而wait,notify,notifyAll不需要捕获异常。

     ④sleep是线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复。

     ⑤wait是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify唤醒指定的线程或者notifyAll唤醒所有线程,才会进入锁池,当获得对象锁,进入运行状态。

 

7.产生线程安全问题

  ①是否是多线程环境

  ②是否有共享数据

  ③是否有多条语句操作共享数据

  以上三个问题同时出现会出现线程安全问题

  解决方案:把多个语句操作共享数据的代码给锁起来,让任意时刻只有一个线程执行即可。

 

8.同步安全的两种机制

  ①Synchronized关键字

    内部对象锁,只有一个相关条件,且每一个对象都有一个内部锁,为了使线程面对的是同一把锁,对象要生成于类中,

    同步方法的锁是this,静态方法的锁是本类的字节码对象class。

    ②ReentrantLock类,实现了Lock接口。它的lock方法(一般放置于try中)和unlock方法(一般放置于finally中)

   主要区别:

   a,lock不再用synchronize代码包装起来

   b.阻塞需要另外一种对象condition

   c.同步和唤醒的对象是condition而不是lock,对应的方法是await和signal

 

  例:

  线程 woker

  synchronize (obj){

    obj.wait();

  }

 

 

  线程manager

  synchronize(obj){

    obj.notify();

  }
  lock.lock();

  condition.await();

  lock.unlock();
 

  lock.lock();

  condition.signal();

  lock.unlock();
 

   lock更加灵活,以前的方式只有一个等待队列,在实际应用中可能需要多个,为了灵活性,lock将同步互斥和等待队列分开来,

  互斥保证在某个时刻有一个线程访问临界区,等待队列负责保存被阻塞的线程。

 

  内部对象只有一个相关条件,wait方法将一个线程添加到等待集中,由锁来管理那些试图进入synchronize方法的线程,

  由条件来管理那些调用wait的线程,一个锁对象可以有一个或多个相关的条件对象。

 

9.线程池

  使用场景:程序中创建了大量生命周期很短的线程,应该使用线程池。

  主要解决处理器单元内多个线程的执行问题,可以显著减少处理器单元的闲置时间,增加单元处理器的吞吐能力,减少并发线程的数目。

  一个线程池包含很多空闲的线程,将runnable对象交给线程池,就会有一个线程调用run方法,当run方法退出后,线程不会死亡,而是在池中准备为下一个请求提供服务。

  java通过Excutors提供四种线程池:

  ①newCachedThreadPool创建一个可缓存线程池,如果线程池的长度超过处理需要,可灵活回收空闲线程,若无可回收,则会建立新线程。

  ②newFixedThreadPool创建一个定长线程,超出的线程会在队列中等待。

  ③newScheduledThreadPool创建一个定长线程,会定期及周期性任务执行。

  ④newSingleThreadPook创建一个单线程的线程池,它会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序完成。

 

  关于线程池:
  线程池初始化时,即将必要数量的线程创建出来,并一直存在,知道整个系统shutdown;
  线程池在初始化时,即创建很少数量的线程当系统压力上去时,并导致当前线程数量不足时,那么会创建新的线程,一旦系统压力降下去,那么部分线程将被销毁。这种线程池的线程数量实在动态变化的。

 

10.线程池与new Thread的比较

  new Thread的弊端:

  ①每次 new Thread新建对象性能差

  ②线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能过多的占用系统资源导致死机。

  ③缺乏更多功能,如定时执行,定期执行,线程中断。

  

  线程池的优势:

  ①重用存在的线程,减少对象创建、消亡的开销,性能佳

  ②可有效控制最大并发线程数,提高系统资源的使用率,减少过多资源竞争,避免阻塞。

  ③提供定时执行、定期执行、单线程、并发数控制等功能。

 

其他
java多线程管理 concurrent包用法详解:http://blog.csdn.net/wulei_longhe/article/details/30032031
 

推荐阅读