java-线程Threads

Threads

知识点:一. 什么是线程:

            进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进程。例如每打开一个
            IE浏览器窗口,就启动了一个新的进程。同样,每次执行JDK的java.exe程序,就启动了一个独立的Java虚拟机进程,该进程
            的任务是解析并执行Java程序代码。
            
            线程是指进程中的一个执行流程。一个进程可以由多个线程组件。即在一个进程中可以同时运行多个不同的线程,它们分别
            执行不同的任务,当进程内的多个线程同时运行时,这种运行方式称为并发运行。

            线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地
            址空间中工作,这些线程可以共享同一块内存和系统资源。比如共享一个对象或者共享已经打开的一个文件。

        二. java中的线程

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

            计算机中机器指令的真正执行者是CPU,线程必须获得CPU的使用权,才能执行一条指令。
        java中大致可以把线程分为前台线程(执行线程)、后台线程(守护线程或者叫精灵线程)

        三. 线程的创建和启动

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

            . 扩展java.lang.Thread类;
            . 实现Runnable接口;

            1.  扩展java.lang.Thread类

                Thread类代表线程类,它的最主要的两个方法是:
                . run()——包含线程运行时所执行的代码;
                . start()——用于启动线程;

                用户的线程类只需要继承Thread类, 覆盖Thread类的run()方法即可。在Thread类中,run()方法的定义如下:

                public void run();    //没有抛异常,所以子类重写亦不能抛异常

                1) 主线程与用户自定义的线程并发运行


                   a. Thread类的run()方法是专门被自身的线程执行的,主线程不能调用Thread类的run()方法,否则违背了Thread类提供run()
                      方法的初衷;
                   b. Thread thread = Thread.currentThread();       返回当前正在执行这行代码的线程引用;
                      String name = thread.getName();               获得线程名字;

                      每个线程都有默认名字,主线程默认的名字为main, 用户创建的第一个线程的默认名字为"Thread-0", 第二个线程
                      的默认名字为"Thread-1", 依引类推。Thread类的setName()方法可以显示地设置线程的名字;

                2) 多个线程可以共享同一个对象的实例变量。
                                     
                
                3) 不要覆盖Thread类的start()方法

                   创建了一个线程对象,线程并不自动开始运行,必须调用它自己的start()方法。对于以下代码:
           //Machine为Thread类的子类
                   Machine machine = new Machine();
                   machine.start();

                   当用new语句创建Machine对象时,仅仅在堆区内出现一个Machine对象,此时Machine线程并没有被
                   启动。当主线程执行Machine对象的start()方法时,该方法会启动Machine线程。


                4) 一个线程只能被启动一次
           //Machine为Thread类的子类
                   Machine machine = new Machine();
                   machine.start();
                   machine.start();         //抛出IllegalThreadStateException异常

            2.  实现Runnable接口

                Java不允许一个类继承多个类,因此一旦一个类继承了Thread类,就不能再继承其他的类。为了解决这一问题,Java提供
                了java.lang.Runnable接口,它有一个run()方法,定义如下:

                public void run();
        
        例如:
        //A类实现了接口Runnable,想启动这个线程类必须依靠一个Thread类的对象
        A a = new A();
        Thread t = new Thread(a);
        t.start();        


        四. 线程状态

            线程在它的生命周期中会处于各种不同的状态;

            1. 新建状态(New)

               用new语句创建的线程对象处于新建状态, 此时它和其他Java对象一样;仅在堆区中被分配了内存;

            2. 就绪状态(Runnable)

               当一个线程对象创建后,其他线程调用它的start()方法, 该线程就进入就绪状态,
               处于这个状态的线程位于可运行池中, 等待获得CPU的使用权。

            3. 运行状态(Running)

               处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中, 如果计算机只有一个CPU, 那么任何时刻只会有一个
               线程处于这个状态。如果计算机有多个CPU, 那么同一时刻可以让几个线程占用不同的CPU,使它们都处于运行状态。只有
               处于就绪状态的线程才有机会转到运行状态。

            4. 阻塞状态(Blocked)

               指线程因为某些原因放弃CPU, 暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新
               进入就绪状态,它才有机会转到运行状态。

               阻塞状态可分为三种:

               . 位于对象等待池中的阻塞状态(Blocked in objects' wait pool): 运行状态时,执行某个对象的wait()方法;
               . 位于对象锁池中的阻塞状态(Blocked in object's lock pool): 当线程处于运行状态,试图获得某个对象的同步锁时,如该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中;
               . 其他阻塞状态(Otherwise Blocked): 当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O
                 请求时,就会进入这个状态。
 
               当一个线程执行System.in.read()方法时,就会发出一个I/O请求,该线程放弃cpu, 进入阻塞状态,直到I/O处理完毕,该线程才会恢复运行。


            5. 死亡状态(Dead)

               当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。线程有可能是正常执行完run()方法退出,也有可能是遇
               到异常而退出。不管该线程正常结束还是异常结束,都不会对其他线程造成影响。


        五. 线程调度

            计算机通常只有一个CPU, 在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的
            并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在可运行池中,会有多个处于就
            绪状态的线程在等待CPU,Java虚拟机的一项任务就是负责线程的调度。线程的调度是指按照特定的机制为多个线程分配CPU
            的使用权。有两种调度模型:

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

            一个线程会因为以下原因而放弃CPU: 

            . Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;
            . 当前线程因为某些原因而进入阻塞状态;
            . 线程运行结束;

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


            1.  stop

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

            2. isAlive: 

               final boolean 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线程阻塞,因为是A线程调用的B.join()这个方法
               谁调用谁阻塞。


        六. 线程的同步

            线程的职责就是执行一些操作,而多数操作都涉及到处理数据。这里有一个程序处理实例变量a: 

            a+=i;
            a-=i;
            System.out.println(a);


      多个线程在操纵共享资源——实例变量时,有可能引起共享资源的况争。为了保证每个线程能正常执行操作,保证共享资
            源能正常访问和修改。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持有的锁;

        九. 线程让步

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

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

            . sleep()不考虑其他线程优先级;
              yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
            . sleep()转到阻塞状态;
        yield()转到就绪状态;
            . sleep()会抛出InterruptedException异常,
              yield()不抛任何异常
            . sleep()比yield方法具有更好的可移植性。对于大多数程序员来说,yield()方法的唯一用途是在测试期间人为地提高程序的
              并发性能,以帮助发现一些隐藏的错误,所以yield()并不常用。

        十. 调整线程优先级
        注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果 
        线程优先级的使用原则与操作系统有着密切的联系因此在JAVA中的线程的调度是完全受其所运行平台的操作系统的线程调度程序控制的。所有虽然我们可以设置线程的优先级但是在运行的时候不一定能够确切的体现出来。
            所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较多的
            运行机会。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()方法,暂停当前线程,已过时;

-----------------------------------------------------------------
关于interrupt、isInterrupted、interrupted三个方法的理解
interrupt和isInterrupted 是Thread类中的非静态方法  可以用线程对象来访问   t.interrupt()     t.isInterrupted()

interrupted 是Thread类中的静态方法  可以用类名来访问   Thread.interrupted()


public void interrupt(){...}
interrupt 可以用来中断线程

public static boolean interrupted(){...}
public boolean isInterrupted(){...}
interrupted  isInterrupted 可以用来判断线程是否被中断过

首先要明白一件事情:
        一个线程A是不可能调用线程B的interrupt方法来中断B线程的运行, 因为线程A调用线程B的interrupt方法的时候,线程A肯定是在使用cup运行这个中断方法的代码,这个时候线程B那就肯定没有在使用CPU运行代码了(同一时刻值只可能有一个线程使用cup在运行代码),所以就谈不上说线程A调用线程B的interrupt方法来把正在运行的线程B给中断了

那么为什么API里面还提供一个interrupt来中断运行的线程呢?
        这里你要明白另外一个事情,那就是一个线程在使用cpu运行的时候,可以自己调用interrupt来中断自己,就是自己让自己立马交出cpu的使用权,然后回到线程的就绪状态。
例如:
public class Test extend Threand{
         public void run(){
                     for(int i=0;i<100;i++){
                              if(a==50){
                                      this.interrupt();
                               }
                              System.out.println("hello world");
                     }
         }

}
当然这个时候你会考虑这样做的意义是什么?当线程运行到一个条件满足的时候,先让自己中断一下,让别的线程有机会在这个满足的条件下进行其他操作,这个操作需求在实际的应用中绝对是会有可能发生的情况。

好了,那么然后下一个问题就是,既然线程A不能调用线程B的interrupt方法来中断线程B,那么我们在线程A中真的调用了线程B的interrupt方法的话,这个操作具有什么意义呢?
        这个时候我们要了解另外一个事情,那就是每一个线程对象中有一个特别的标记,那就是可以标记出这个线程有没有被其他线程进行尝试中断过,因为线程A中是可以调用线程B的interrupt方法来尝试中断线程B的,虽然这样的中断并不能起真正的中断作用(这个上面已说明过了),但是可以把线程B对象中的按个特别的标识从false改为true,表示在某个情况满足下有其他线程想中断你了。
那么也就是说我们可以在线程A中通知线程B,我在某个条件满足的情况 下至少尝试着调用你的interrupt方法去中断你了,然后这个时候线程B在运行的时候过程中就可以通过另外俩个方法isInterrupted和 interrupted来知道是否有其他线程在试图中断自己了,然后就是线程B根据这个信息,来自己决定是否要中断自己(自己可以调用自己的 interrupted方法来中断自己),或者不理不睬这样的信息通知而去继续运行下去,具体是要进行那种选择,那么就要看你到时候的具体的业务逻辑需求 了。
      
      特别说明的就是isInterrupted和interrupted这俩个方法,interrupted方法是静态方法,可以用类名来调用,当在线程B执 行的代码中调用Thread.interrupted()方法的时候,会显示出是否有其他线程视图中断自己也就是线程B,如果显示为true,注意了,这 个时候Thread.interrupted()会接着把这个状态信息从true改为false,因为线程B可能被另外多个线程多次中断,这个方法就可以让我 们把之前的中断信息给抹去,因为有可能线程B里面根本就不想理会之前的中断信息.isInterrupted是非静态方法,可以用对象来调用,它也能返回 true或者false来表示线程B是否有被其他线程尝试中断的情况,但是这里并不会修改这个信息,也就是如果这时候显示为true,那么之后也会显示为 ture,这一点和静态方法interrupted不一样的.
例如:   这个例子表示如果没有其他线程试图中断我的时候,我就会一直运行下去,如果有其他线程视图中断我(也许是业务需要),那么我就会停止运行
public void run(){    
        try{    
             ....    
             while(!Thread.currentThread().isInterrupted){    
                    // do more work;    
             }    

        }catch(InterruptedException e){    
                    // 如果线程在sleep或者wait期间被打断那么会抛出异常    
        }    
        finally{    
                   // 最后处理一下必要的事情    

        }    
}  


最后还有一个问题,那就是为什么线程在wait或者sleep期间被中断时候会抛出InterruptedException呢?
因 为处于这些状态的线程是不可能拿到cup的使用权的,那么就意味着即使我调用你的interrupt方法去尝试的通知你说:"我试图在中断你",那么你也是不可能 拿到cup的使用权去处理的我的这种中断的通知信息的,所以这时候就会抛出异常了(当然我们也可以利用这特点来改变一个阻塞状态线程的状态)。

当然这里所描述的一些细节情况可能学生们不能马上理解,因为它的实际用途和应用需要在具体业务逻辑业务中才能体现出其真正的作用和意义
 

猜你喜欢

转载自blog.csdn.net/bifuguo/article/details/81901228