线程详解

来源:互联网 发布:网络机顶盒ir接口 编辑:程序博客网 时间:2024/06/11 06:12
一. 理解多线程:

       进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。

  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

  线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

  多进程是指操作系统能同时运行多个任务(程序)。

  多线程是指在同一程序中有多个顺序流在执行。

       java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。




    (1)多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。

 

     (2)线程又称为轻量级进程,它和进程一样拥有独立的执行控制权,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程

             中的其它线程共享一个存储空间,这使得线程间的通信较进程简单。


    (3)多个线程的执行是并发的,也就是逻辑上“同时”,不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不可能

             的,但是由于CPU的速度非常快,用户感觉不到其中的区别,因此我们也不用关心它,只需要设想各个线程是同时执行即可。

 

     (4)多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,由此带来

             的线程调度,同步等问题。


二、线程创建方法:

    (1) 继承 Thread 类,覆盖方法 run(): 

    (2) 实现 Runnable 接口:  


三、线程的四种状态

    1、新状态:线程已被创建但尚未执行(start() 尚未被调用)。 

    2、可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。

    3、死亡状态:正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强

             制终止,不会释放锁。

    4、阻塞状态:线程不会被分配 CPU 时间,无法执行。


四、线程的优先级:

        线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得 CPU 时间时,线程调度系统根据各个线程的优先级来决定

      给谁分配 CPU 时间,优先级高的线程有更大的机会获得 CPU 时间,优先级低的线程也不是没有机会,只是机会要小一些罢了。你可以调用

      Thread 类的方法 getPriority() 和 setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,

      缺省是5(NORM_PRIORITY)。


五、线程的同步

        由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲

     突,有效避免了同一个数据被多个线程同时访问。这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和

     synchronized 代码块。


    由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲

     突,有效避免了同一个数据被多个线程同时访问。这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和

     synchronized 代码块。

 

  (1)synchronized 方法:通过在方法声明中加入 synchronized关键字,如:public synchronized void accessVal(int newVal);

 

            synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法所属类实例的锁

            才能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后,被阻塞的线程方能获得该锁,重新进

            入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态

           (因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明

            为 synchronized)。

 

            在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成

            员变量的访问。

 

            synchronized 方法的缺陷:若将一个方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为

            synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然

            我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java

            为我们提供了更好的解决办法,那就是 synchronized块。

 

  (2)synchronized代码块:通过 synchronized 关键字来声明synchronized代码块。

 

             synchronized(syncObject) {
                         //允许访问控制的代码
             }

             synchronized 代码块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁才能执行,具体

             机制同前所述。

 

             由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

六. 线程的阻塞:

 

     为了解决对共享存储区的访问冲突,Java 引入了同步机制,现在让我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为在任

     意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题,

     Java 引入了阻塞机制。 阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一定已经很熟悉了。

     Java 提供了大量方法来支持阻塞。

 

    (1)sleep() 方法:sleep() 允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定

            的时间一过,线程重新进入可执行状态。典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间

            后重新测试,直到条件满足为止。

 

    (2)suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被

            调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还

            没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。

 

    (3)yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。

            调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。

 

    (4)wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为

            参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调

            用。

 

     sleep() 方法和suspend() 方法阻塞时都不会释放占用的锁,当睡眠结束或调用resume() 方法该线程继续执行。

 

     wait() 方法导致线程阻塞,并且该对象上的锁被释放。当调用notify() 方法解除阻塞。

 

     关于 wait() 和 notify() 方法的两点说明:

 

     第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选

              择,所以编程时要特别小心,避免因这种不确定性而产生问题。

 

     第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法

              而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

 

七. 守护线程:

 

     守护线程是一类特殊的线程,它和普通线程的区别在于它并不是应用程序的核心部分,当一个应用程序的所有非守护线程终止运行时,即使仍然有

     守护线程在运行,应用程序也将终止,反之,只要有一个非守护线程在运行,应用程序就不会终止。守护线程一般被用于在后台为其它线程提供服

     务。可以通过调用方法 isDaemon() 来判断一个线程是否是守护线程,也可以调用方法 setDaemon() 来将一个线程设为守护线程。

 

八. 线程组:

 

     线程组是一个 Java 特有的概念,在 Java 中,线程组是类ThreadGroup 的对象,每个线程都隶属于唯一一个线程组,这个线程组在线程创建时

     指定并在线程的整个生命期内都不能更改。你可以通过调用包含 ThreadGroup 类型参数的 Thread 类构造函数来指定线程属的线程组,若没有

     指定,则线程缺省地隶属于名为 system 的系统线程组。

 

     我们可以通过调用线程组的相应方法来设置其中所有线程的优先级,也可以启动或阻塞其中的所有线程。线程组机制也允许我们通过分组来区分有

     不同安全特性的线程,对不同组的线程进行不同的处理,还可以通过线程组的分层结构来支持不对等安全措施的采用。Java 的 ThreadGroup 类

     提供了大量的方法来方便我们对线程组树中的每一个线程组以及线程组中的每一个线程进行操作。

 

九、常用函数说明

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

    2、join() : 指等待t线程终止

        使用方式。

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

   Thread t = new AThread(); t.start(); t.join();
  <span style=" font-family: Helvetica, 'Hiragino Sans GB', 微软雅黑, 'Microsoft YaHei UI', SimSun, SimHei, arial, sans-serif; line-height: 1.6;"><strong>    </strong></span><span style=" font-family: Helvetica, 'Hiragino Sans GB', 微软雅黑, 'Microsoft YaHei UI', SimSun, SimHei, arial, sans-serif; line-height: 1.6;"><strong>为什么要用join()方法</strong></span>

        在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。 


0 0
原创粉丝点击