Java多线程总结

参考:

 http://www.cnblogs.com/wxd0108/p/5479442.html 
 http://blog.csdn.net/evankaka/article/details/44153709 
 https://blog.csdn.net/u012156116/article/details/79390864

1.线程与进程的区别

定义:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。


进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

  • 1 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
  • 2 线程的划分尺度小于进程,使得多线程程序的并发性高。
  • 3 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  • 4 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 5 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

这些是比较字面的描述,更加深刻的认识可以参考:线程-进程

2.Java中线程的几种状态

在Java中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡,可见下图:


下面是各个状态的简单介绍:

   1.新建状态(new): 新创建了一个线程对象。

   2.就绪状态(Runnable): 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

   3 .运行状态(Running):  就绪状态的线程获取了CPU,执行程序代码。

   4.阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

     线程在Running的过程中可能会遇到阻塞(Blocked)情况:

  1.  调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。(注意,sleep是不会释放持有的锁)
  2.  等待阻塞:调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
  3.  同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

   5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3.线程调度的相关知识

线程的调度

1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
 
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
[plain]  view plain  copy
  1. static int MAX_PRIORITY  
  2.           线程可以具有的最高优先级,取值为10。  
  3. static int MIN_PRIORITY  
  4.           线程可以具有的最低优先级,取值为1。  
  5. static int NORM_PRIORITY  
  6.           分配给线程的默认优先级,取值为5。  

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
 每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
 
2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
 
3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
 
4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
 
5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
 
6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
 注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

4.Java中线程调度的常用API

①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

②join():指等待t线程终止。

      join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

[java]  view plain  copy
  1. Thread t = new AThread(); t.start(); t.join();  

 ③yield(): 暂停当前正在执行的线程对象,并执行其他线程。

     yield()与sleep()方法类似,只是不能指定暂停多久,并且yield()方法只能让同优先级的方法获取运行的机会,需要注意的是,yield()只是让线程由运行状态转到就绪状态,但是可能并未有效果(该线程再次拿到cpu使用权)。

      yield()与sleep()的区别:sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这个时间长短可以由用户设定,允许较低优先级的线程获得运行机会;而yield方法使当前线程让出 CPU 占有权,让出的时间是不可设定的,并且只有相同优先级的线程能够获取cpu占有权,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

④setPriority(): 更改线程的优先级。

            MIN_PRIORITY = 1
      NORM_PRIORITY = 5

           MAX_PRIORITY = 10

用法:

[java]  view plain  copy
  1. Thread4 t1 = new Thread4("t1");  
  2. Thread4 t2 = new Thread4("t2");  
  3. t1.setPriority(Thread.MAX_PRIORITY);  
  4. t2.setPriority(Thread.MIN_PRIORITY);  

⑤interrupt():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!

⑥wait(),notify(),synchronized()

此三个方法是任何对象都拥有的同步工具,

monitor:

首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。

wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。

用法:

  • synchronized单独使用:
    • 代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容
      public class Thread1 implements Runnable {
         Object lock;
         public void run() {  
             synchronized(lock){
               ..do something
             }
         }
      }
    • 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。
      public class Thread1 implements Runnable {
         public synchronized void run() {  
              ..do something
         }
      }
  • synchronized, wait, notify结合:循环打印A,B:

[java]  view plain  copy
  1. public class MythreadPrinter implements Runnable {  
  2.     private String name;  
  3.     private Object prev;  
  4.     private Object self;  
  5.   
  6.   
  7.     @Override  
  8.     public void run() {  
  9.         int count = 3;  
  10.         while (count>0){  
  11.             synchronized (prev){  
  12.                 synchronized (self){  
  13.                     System.out.println(name);  
  14.                     count--;  
  15.                     self.notify();  
  16.                 }  
  17.                 try {  
  18.                     prev.wait();  
  19.                 } catch (InterruptedException e) {  
  20.                     e.printStackTrace();  
  21.                 }  
  22.             }  
  23.         }  
  24.     }  
  25.   
  26.     public static void main(String[] args) throws Exception{  
  27.         Object a = new Object();  
  28.         Object b = new Object();  
  29.         MythreaPrinter pa = new MythreaPrinter("A",b,a);  
  30.         MythreaPrinter pb = new MythreaPrinter("B",a,b);  
  31.   
  32.         new Thread(pa).start();  
  33.         Thread.sleep(100);  
  34.         new Thread(pb).start();  
  35.         Thread.sleep(100);  
  36.     }  
  37. }  

补充: volatile关键字

多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。


针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。

(7). 其他常用方法介绍:

       sleep(): 强迫一个线程睡眠N毫秒。 
  isAlive(): 判断一个线程是否存活。 
  join(): 等待线程终止。 
  activeCount(): 程序中活跃的线程数。 
  enumerate(): 枚举程序中的线程。 
       currentThread(): 得到当前线程。 
  isDaemon(): 一个线程是否为守护线程。 
  setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
  setName(): 为线程设置一个名称。 
  wait(): 强迫一个线程等待。 
  notify(): 通知一个线程继续运行。 

  setPriority(): 设置一个线程的优先级。

5.线程数据传递(通过构造方法、通过变量和方法、通过回调函数)

猜你喜欢

转载自blog.csdn.net/qq_27139155/article/details/79911125