线程学习笔记

来源:互联网 发布:淘宝商品与描述不符 编辑:程序博客网 时间:2024/05/29 08:18

 

1. 线程基础
   1.1 线程的概念模型
   2.1 创建线程两种方式
   3.1 后台线程
   4.1 GUI线程
2. 线程控制
   2.1 线程状态
   2.2 线程优先级
   2.3 线程串行化
   2.4 线程休眠
   2.5 线程让步
   2.6 线程挂起和恢复
3. 线程的同步
   3.1 临界资源问题
   3.2 互持锁
   3.3 死锁
   3.4 线程同步通讯
   3.5 生产者-消费者问题
4. 多线程编程专题
   4.1 线程间数据传输
   4.2 类同步性与线程安全
   4.3 定时器

 

线程是一个程序内部的顺序控制流
   线程和进程

  1. 每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大
  2. 线程:轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
  3. 多进程:在操作系统中能同时运行多个任务(程序)
  4. 多线程:在同一个应用程序中有多个数据流同时执行

线程的概念模型
    1. 虚拟的CPU,由java.lang.Thread类封装和虚拟
    2. CPU所执行的代码,传递给Thread类对象
    3. CPU所处理的数据,传递给Thread类对象

 

Java的线程是通过java.lang.Thread类来实现的.当我们生成一个Thread类的对象之后,一个新的线程就产生了.
此线程实例表示Java解释器中的真正的线程,通过它可以启动线程,终止线程,线程挂起等,每个线程都是通过类Thread在Java的软件包Java.lang中定义,它的构造方法为:
public Thread (ThreadGroup group,Runnable target,String name);
其中,group 指明该线程所属的线程组;target实际执行线程体的目标对象,它必须实现接口Runnable; name为线程名.Java中的每个线程都有自己的名称,Java提供了不同Thread类构造器,允许给线程指定名称.如果name为null时,则Java自动提供唯一的名称.
当上述构造方法的某个参数为null时,我们可得到下面的几个构造方法:
public Thread ();
public Thread (Runnable target);
public Thread (Runnable target,String name);
public Thread (String name);
public Thread (ThreadGroup group,Runnable target);
public Thread (ThreadGroup group,String name);
一个类声明实现Runnable接口就可以充当线程体,在接口Runnable中只定义了一个方法 run():
public void run();
任何实现接口Runnable的对象都可以作为一个线程的目标对象,类Thread本身也实现了接口Runnable,因此我们可以通过两种方法实现线程体.
(一)定义一个线程类,它继承线程类Thread并重写其中的方法 run(),这时在初始化这个类的实例时,目标target可为null,表示由这个实例对来执行线程体.由于Java只支持单重继承,用这种方法定义的类不能再继承其它父类.
(二)提供一个实现接口Runnable的类作为一个线程的目标对象,在初始化一个Thread类或者Thread子类的线程对象时,把目标对象传递给这个线程实例,由该目标对象提供线程体 run().这时,实现接口Runnable的类仍然可以继承其它父类.
每个线程都是通过某个特定Thread对象的方法run( )来完成其操作的,方法run( )称为线程体.图6.2表示了java线程的不同状态以及状态之间转换所调用的方法.

 

创建线程

 

第一种方式:
Java的线程是通过java.lang.Thread类来实现的
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体

public clas TeatThread{
     public static void main(String[] args){
 Runner1 r = new Runner1();
        Thread t = new Thread(r);
        t.start();
     }
}

class Runner1 implements Runnable{
     public void run(){
          for(int i=0;i<30;i++){
              System.out.println("No. "+i);
          }
      }
}

 

例子

/*
 *  有三个线程,一个是main线程,还有两个是它的子线程t1和t2
 */

class TestThread2 {
 public static void main(String args[])
 {
  Runner2 r = new Runner2();
  Thread t1 = new Thread(r);  //如果连续两次调用实现了runnable接口的类对象的run()方法,即r.run(); r.run();
  Thread t2 = new Thread(r);  //他们将不会创建新线程,而都是main线程
  t1.setName("first Thread"); //默认线程名为Thread-x 格式,其中x为整数
  t2.setName("second Thread");
  t1.start();
  t2.start();
  //如果没有如下代码,main()方法将退出main方法线程
     Thread.currentThread().setName("main Thread"); //main方法的默认线程名为main
     for(int i=0;i<20;i++){
   String s = Thread.currentThread().getName();
   System.out.println(s+":"+i);
  }
 }
}

class Runner2 implements Runnable{
 public void run(){
  for(int i=0;i<20;i++){
   String s = Thread.currentThread().getName();
   System.out.println(s+":"+i);
  }
 }
}

 

  说明:
        程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用Runner2的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
        在一个方法中调用Thread.currentThread().getName()方法,可以获取当前线程的名字。在mian方法中调用该方法,获取的是主线程的名字。
        注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
        Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
        实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

 

 

 


  Runner2 r = new Runner2();
  Thread t1 = new Thread(r);  

  Thread t2 = new Thread(r); 

线程CPU数据t1Thread类对象t1Runner类中的run方法Runner类型对象rt2Thread类对象t2Runner类中的run方法Runner类型对象r

 

class MyRunner implements Runnable{...}
class YourRunner implements Runnable{...}

 

MyRunner m = new MyRunner();
YourRunner y1 = new YourRunner();
YourRunner y2 = new YourRunner();

 

Thread t1 = new Thread(m);   //t1和t2, 代码不同,数据不同
Thread t2 = new Thread(y1);  //t2和t3, 代码相同(run()),数据不同
Thread t3 = new Thread(y2);  //t3和t4, 代码相同,数据相同
Thread t4 = new Thread(y2);

 

 

创建线程

第二种方式:

直接继承Thread类(该类实现了Runnable接口)创建线程

 

public clas TeatThread3{
     public static void main(String[] args){
        Thread t1 = new Runner3();
        t.start();
     }
}

class Runner3 extends Thread{
     public void run(){
          for(int i=0;i<30;i++){
              System.out.println("No. "+i);
          }
      }
}

 

两种方式比较
一 使用Runnable接口创建线程
     1. 可以将CPU, 代码和数据分开, 形成清晰的模型
     2. 线程体run()方法所在的类还可以从其他类继承一些有用的属性或方法;
     3. 并有利于保持程序风格的一致性。

二 直接继承Thread类创建线程
     1. Thread子类无法再从其他类继承
     2. 编写简单,run()方法的当前对象就是线程对象,可直接操纵 

 

后台线程
后台处理(Background Processing)-处理低优先级的任务
后台线程(Background Thread / Daemon Thread)-提供服务的线程
用户线程(User Thread)
主线程(Main Thread)
子线程(Sub Thread)

 

Thread类提供的相关的方法:
public final Boolean isDaemon()
public final void setDaemon(Boolean b)

 

 t2.setDaemon(true); //设置为后台线程,默认为用户线程
当用户线程执行成功或失败而退出时,jVM也退出,因此后台线程有可能没有执行完成。
比如:服务员在上菜,客人都走了,服务员就没必要再上菜了。就可以直接结束了

 

 

1. 创建状态(new Thread)
执行下列语句时,线程就处于创建状态:
Thread myThread = new MyThreadClass( );
当一个线程处于创建状态时,它仅仅是一个空的线程对象,系统不为它分配资源.
2. 可运行状态( Runnable )
Thread myThread = new MyThreadClass( );
myThread.start( );
当一个线程处于可运行状态时,系统为这个线程分配了它需的系统资源,安排其运行并调用线程运行方法,这样就使得该线程处于可运行( Runnable )状态.需要注意的是这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行.由于很多计算机都是单处理器的,所以要在同一时刻运行所有的处于可运行状态的线程是不可能的,Java的运行系统必须实现调度来保证这些线程共享处理器.
3. 不可运行状态(Not Runnable)
进入不可运行状态的原因有如下几条:
1) 调用了sleep()方法;
2) 调用了suspend()方法;
3) 为等候一个条件变量,线程调用wait()方法;
4) 输入输出流中发生线程阻塞;
不可运行状态也称为阻塞状态(Blocked).因为某种原因(输入/输出,等待消息或其它阻塞情况),系统不能执行线程的状态.这时即使处理器空闲,也不能执行该线程.
4. 死亡状态(Dead)
线程的终止一般可通过两种方法实现:自然撤消(线程执行完)或是被停止(调用stop()方法).目前不推荐通过调用stop()来终止线程的执行,而是让线程执行完.

 

 

 

读解Thread类API
static int MAX_PRIORITY
          线程可以具有的最高优先级。
static int MIN_PRIORITY
          线程可以具有的最低优先级。
static int NORM_PRIORITY
          分配给线程的默认优先级。
        构造方法摘要
Thread(Runnable target)
          分配新的 Thread 对象。
Thread(String name)
          分配新的 Thread 对象。
        方法摘要
static Thread currentThread()
          返回对当前正在执行的线程对象的引用。
ClassLoader getContextClassLoader()
          返回该线程的上下文 ClassLoader。
long getId()
          返回该线程的标识符。
String getName()
          返回该线程的名称。
int getPriority()
          返回线程的优先级。
Thread.State getState()
          返回该线程的状态。
ThreadGroup getThreadGroup()
          返回该线程所属的线程组。
static boolean holdsLock(Object obj)
          当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
void interrupt()
          中断线程。
static boolean interrupted()
          测试当前线程是否已经中断。
boolean isAlive()
          测试线程是否处于活动状态。
boolean isDaemon()
          测试该线程是否为守护线程。
boolean isInterrupted()
          测试线程是否已经中断。
void join()
          等待该线程终止。
void join(long millis)
          等待该线程终止的时间最长为 millis 毫秒。
void join(long millis, int nanos)
          等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
void resume()
          已过时。 该方法只与 suspend() 一起使用,但 suspend() 已经遭到反对,因为它具有死锁倾向。有关更多信息,请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对?。
void run()
          如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
void setContextClassLoader(ClassLoader cl)
          设置该线程的上下文 ClassLoader。
void setDaemon(boolean on)
          将该线程标记为守护线程或用户线程。
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
          设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
void setName(String name)
          改变线程名称,使之与参数 name 相同。
void setPriority(int newPriority)
          更改线程的优先级。
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
          设置该线程由于未捕获到异常而突然终止时调用的处理程序。
static void sleep(long millis)
          在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
static void sleep(long millis, int nanos)
          在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)。
void start()
          使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
void stop()
          已过时。 该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依 次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。有关更多信息,请参阅《为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。
void stop(Throwable obj)
          已过时。 该方法具有固有的不安全性。请参阅 stop() 以获得详细信息。该方法的附加危险是它可用于生成目标线程未准备处理的异常(包括若没有该方法该线程不太可能抛出的已检查的异常)。有关更多信息,请参阅 为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对?。
void suspend()
          已过时。 该方法已经遭到反对,因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前任何线程都不能访问 该资源。如果重新开始目标线程的线程想在调用 resume 之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。有关更多信息,请参阅为何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反对?。
String toString()
          返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
static void yield()
          暂停当前正在执行的线程对象,并执行其他线程。


线程的状态转换图
        线程在一定条件下,状态会发生变化。线程变化的状态转换图如下:

2007051811.png

1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。调用start后不一定立马运行

                                         该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。

                                      直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
        (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
        (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
        (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。

                    当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

 


线程的调度
        1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
                Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
                        static int MAX_PRIORITY=10       线程可以具有的最高优先级

                        static int MIN_PRIORITY=1         线程可以具有的最低优先级
                        static int NORM_PRIORITY=5      分配给线程的默认优先级
        Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
        每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
        线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

        Java中不能通过优先级来控制运行顺序。


        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中已经废除,不再介绍。因为有死锁倾向。
        7、常见线程名词解释
        主线程:JVM调用程序mian()所产生的线程。
        当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
        后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
        前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台 线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

 

 

 线程的串行化
在多线程程序中,如果在一个线程运行的过程中要用到另一个线程的运行结果,则可进行线程的串行化处理。
Thread类提供的相关方法:
public final void join()
public final void join(long millis)
public final void join(long millis, int nanos)

 

class TestjOIN {
 public static void main(String args[])
 {
         Runner2 r = new Runner2();
         Thread t1 = new Thread(r); 
         t1.start();
        
         try{                //只有执行完子线程之后,才会执行主线程中剩余的部分,因此不会出现两个for循环交替现象。
              t1.join();     //如果在join中指定等待时间比如join(5,100),t1运行5毫秒100纳秒之后,两个线程交替运行
         }catch(InterruptedException e){
              e.printStackTrace();
         }

 

         for(int i=0;i<20;i++){
         String s = Thread.currentThread().getName();
         System.out.println(s+":"+i);
  }
 }
}

class Runner2 implements Runnable{
     public void run(){
          for(int i=0;i<20;i++){
                String s = Thread.currentThread().getName();
                System.out.println(s+":"+i);
         }
     }
}

 

 

 

 

 

原创粉丝点击