JAVA线程

线程:
        一. 从进程到线程
            进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进程。
            线程是指进程中的一个执行流程。一个进程可以由多个线程组成,在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务。
            当进程内的多个线程同时运行时,这种运行方式称为并发运行。


            线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。


        二. java中的线程
            在java虚拟机进程中,执行程序代码的任务是由线程来完成的。每当用java命令启动一个Java虚拟机进程时,Java虚拟机都 会创建一个主线程。该线程从程序入口main()方法开始执行。


            java中可以把线程分为前台线程(执行线程)、后台线程(守护线程)。


        三. 线程的创建和启动
            Java虚拟机的主线程从启动类的main()方法开始运行。此外,用户还可以创建自己的线程,它将和主线程并发运行。创建线程有两种方式:


            . 继承java.lang.Thread类; //extends
            . 实现Runnable接口;//implements


            1.  继承java.lang.Thread类
                1)重写Thread类的run()方法;//public void run(){}——包含线程运行时所执行的代码;
                2)在main()方法中调用start()方法启动线程,一个线程只能被启动一次。
------------------------------------------------------------------------------------------
例:
public class ThreadTest extends Thread {
   public void run(){
  System.out.println("thread");
   }
   public static void main(String[] args) {
ThreadTest t=new ThreadTest();
t.start();
   }
}
------------------------------------------------------------------------------------------


            2.  实现Runnable接口
                Java只能继承一个类,如果继承了Thread类,就不能继承其他类了。因此可以通过实现java.lang.Runnable接口避免此问题。
1)实现Runnable接口
2)创建Thread对象,传入要启动的线程类
3)调用Thread对象的start()方法  //接口中没有start()方法,不能直接启动
------------------------------------------------------------------------------------------
例:
public class ThreadTest implements Runnable {
   public void run(){
  System.out.println("thread");
   }
   public static void main(String[] args) {
ThreadTest tt=new ThreadTest();
Thread td=new Thread(tt);
td.start();
   }
}
------------------------------------------------------------------------------------------




        四. 线程状态


            线程的五种状态;


            1. 新建(New)——用new刚创建出的线程对象,只被分配了内存
            2. 就绪(Runnable)——调用start()方法后,位于可运行池,等待CPU的使用权。
            3. 运行(Running)——占用CPU,执行程序代码。
            4. 阻塞(Blocked)——线程因为某些原因放弃CPU,暂时停止运行。

               阻塞状态可分为三种:
               1)位于对象等待池中的阻塞状态(Blocked in objects' wait pool): 运行状态时,执行某个对象的wait()方法;
               2)位于对象锁池中的阻塞状态(Blocked in object's lock pool): 当线程处于运行状态,试图获得某个对象的同步锁时,如该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中;
               3)其他阻塞状态(Otherwise Blocked): 当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O
                 请求时,就会进入这个状态。
               
  线程从阻塞状态只能进入就绪状态,然后才有机会转到运行状态。


            5. 死亡(Dead)——线程执行完run()方法或执行期间遇到异常退出run()方法。


        五. 线程调度

   按照特定的机制为多个线程分配CPU的使用权。
   
有两种调度模型:
     . 分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
             . 抢占式调度模型:优先让可运行池中优先级高的线程较多可能占用CPU(概率高),如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于可运行状态的线程会一直运行,直至它不得不放弃CPU。Java虚拟机采用这种。


            一个线程会因为以下原因而放弃CPU: 
            . Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;(interrupt)
            . 当前线程因为某些原因而进入阻塞状态; (sleep   join)
            . 线程运行结束;


            线程的调度还依赖于操作系统。在某些操作系统中,只要运行中的线程没有阻塞, 就不会放弃CPU;在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行机会。


            常用方法:
            

            1.  stop();

                 可以强制终止一个线程,但从JDK1.2开始废弃了stop()方法。在实际编程中,一般是在受控制的线程中定义一个标志变量,其他线程通过改变标志变量的值,来控制线程的自然终止、暂停及恢复运行。



            2. isAlive(); 

                判定某个线程是否是活着的(该线程如果处于可运行状态、运行状态和阻塞状态、对象等待队列和对象的锁池中返回true)



            3. Thread.sleep(5000);//参数单位为毫秒 

                放弃CPU, 转到阻塞状态。当结束睡眠后,首先转到就绪状态,如有其它线程在运行,不一定运行,而是在可运行池中等待获得CPU。

                线程在睡眠时如果被中断,就会收到一个InterrupedException异常,线程跳到异常处理代码块。


            4. void sleepingThread.interrupt():
                中断某个线程


            5. boolean otherThread.isInterrupted():
                测试某个线程是否被中断,与static boolean  interrupted()不同,对它的调用不会改变该线程的“中断”状态。


   
            6. public void join();
                public void join(long timeout);
                挂起当前线程,直至它所调用的线程终止才被运行。
                线程A中调用线程B.join(),是使A线程阻塞,B线程开始执行。
                谁调用谁阻塞。


        六. 线程的同步


      多个线程在操纵共享资源——实例变量时,有可能引起共享资源的况争。为了保证每个线程能正常执行操作,保证共享资源能正常访问和修改。Java引入了同步进制,具体做法是在有可能引起共享资源竞争的代码前加上synchronized标记。这样的代码被称为同步代码块。


       每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。当一个线程试图执行带有synchronized标记的代码块时,该线程必须首先获得this关键字引用的对象的锁。
               如果这个锁已经被其他线程占用,Java虚拟机就会把这个线程放到this指定对象的锁池中,线程进入阻塞状态。在对象的锁池中可能会有许多等待锁的线程。等到其他线程释放了锁,Java虚拟机会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。

               假如这个锁没有被其他线程占用,线程就会获得这把锁,开始执行同步代码块。在一般情况下,线程只有执行完同步代码块,才会释放锁,使得其他线程能够获得锁。


                如果一个方法中的所有代码都属于同步代码,则可以直接在方法前用synchronized修饰。


           public synchronized String pop(){...}
      等价于
            public String pop(){
                   synchronized(this){...}
            }


            线程同步的特征:


            1. 如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。
                因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。
            2. 每个对象都有唯一的同步锁。
            3. 在静态方法前面也可以使用synchronized修饰符。此时该同步锁的对象为类对象(类的Class对象)。
    4. 当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块的线程也可以执行
               Thread.sleep()或者执行Thread.yield()方法,此时它并没有释放锁,只是把运行机会(即CPU)让给了其他的线程。
            5. synchnozied声明不会被继承。


    同步是解决共享资源竞争的有效手段。当一个线程已经在操纵共享资源时,其他共享线程只能等待。为了提升并发性能,应该使同步代码块中包含尽可能少的操作,使得一个线程能尽快释放锁,减少其他线程等待锁的时间。




        七. 线程的通信



            锁对象.wait(): 执行该方法的线程释放对象的锁,Java虚拟机把该线程放到该对象的     等待池中。该线程等待其它线程将它唤醒;
            锁对象.notify(): 执行该方法的线程唤醒在对象的等待池中等待的一个线程。Java虚拟机从对象的等待池中随机选择一个线程,把它转到对象的锁池中。如果对象的等待池中没有任何线程,那么notify()方法什么也不做。
            锁对象.notifyAll():会把对象的等待池中的所有线程都转到对象的锁池中。
   注意:notify notifyAll只会唤醒等待池中等待同一个锁对象的线程,因为同一个时刻在等到池中可能会有多个线程,而这多个线程可能是在等待不同的锁对象。

            假如t1线程和t2线程共同操纵一个s对象,这两个线程可以通过s对象的wait()和notify()方法来进行通信。通信流程如下:


            1. 当t1线程执行对象s的一个同步代码块时,t1线程持有对象s的锁,t2线程在对象s的锁池中等待;
            2. t1线程在同步代码块中执行s.wait()方法, t1释放对象s的锁,进入对象s的等待池;
            3. 在对象s的锁池中等待锁的t2线程获得了对象s的锁,执行对象s的另一个同步代码块;
            4. t2线程在同步代码块中执行s.notify()方法,Java虚拟机把t1线程从对象s的等待池移到对象s的锁池中,在那里等待获得锁。
            5. t2线程执行完同步代码块,释放锁。t1线程获得锁,继续执行同步代码块。




        八. 线程的死锁


            A线程等待B线程持有的锁,而B线程正在等待A持有的锁;
--------------------------------------------------------------------------------------------------
死锁的例子:
package t;


public class deadlock extends Thread {
  public static String x="x";
  public static String y="y";
  int a;
  public deadlock(int a){
 this.a=a;
  }
  public void run(){
 if(a==1){
 synchronized(x){
 System.out.println("1 in x");
 //sleep(1000);
 synchronized(y){
 System.out.println("1 in y");
 }
 }
 }
 if(a==2){
 synchronized(y){
 System.out.println("2 in y");
 //sleep(1000);
 synchronized(x){
 System.out.println("2 in x");
 }
 }
 }
  }
  public static void main(String[] args) {
deadlock d1=new  deadlock(1);
deadlock d2=new  deadlock(2);
d1.start();
d2.start();
  }
}
//大概率会死锁,也可能不会锁。加上sleep后则一定会死锁,但需要捕获异常InterruptedException e。
--------------------------------------------------------------------------------------------------


        九. 线程让步


            Thread.yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。


            sleep()和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。区别:


            . sleep()不考虑其他线程优先级;
              yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
            . sleep()转到阻塞状态;
    yield()转到就绪状态;
            . sleep()会抛出InterruptedException异常,
              yield()不抛任何异常
            . sleep()比yield方法具有更好的可移植性,yield()只在测试时用。


        十. 调整线程优先级
   
注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果 
        Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。


            . MAX_PRIORITY: 10, 最高;
            . MIN_PRIORITY: 1, 最低;
            . NORM_PRIORITY: 5, 默认优先级;


   其它:stop():         中止线程运行;            已过时
              resume():    使暂停线程恢复运行; 已过时
              suspend():  暂停线程,不释放锁; 已过时


            释放对象的锁:


            . 执行完同步代码块;
            . 执行同步代码块过程中,遇到异常而导致线程终止,释放锁;
            . 执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;


            线程不释放锁:


            . Thread.sleep()方法,放弃CPU,进入阻塞状态;
            . Thread.yield()方法,放弃CPU,进入就绪状态;
            . suspend()方法,暂停当前线程,已过时;

-------------------------------------------------------------------------------------------------

总结:

 *线程Thread调用的常用方法:isAlive,sleep,interrupt,isInterrupted,join,yield。(stop,suspend,resume)

 *锁对象synchronized调用的方法:wait,notify,notifyAll。
-------------------------------------------------------------------------------------------------
posted @ 2017-12-19 20:53  codeToSuccess  阅读(54)  评论(0编辑  收藏  举报