`
blueram
  • 浏览: 762392 次
  • 性别: Icon_minigender_1
  • 来自: 郑州
社区版块
存档分类
最新评论

java中的多线程总结

 
阅读更多


多线程目录

 

<!--[if !supportLists]-->1、  <!--[endif]-->线程的简单介绍

<!--[if !supportLists]-->2、  <!--[endif]-->线程的实现方式

<!--[if !supportLists]-->3、  <!--[endif]-->Daemon线程

<!--[if !supportLists]-->4、  <!--[endif]-->线程的生命周期

<!--[if !supportLists]-->5、  <!--[endif]-->线程的加入(join

<!--[if !supportLists]-->6、  <!--[endif]-->线程的停止

<!--[if !supportLists]-->7、  <!--[endif]-->线程异常处理

<!--[if !supportLists]-->8、  <!--[endif]-->线程同步

<!--[if !supportLists]-->9、  <!--[endif]-->Wait()notify()

 

1、线程的简单介绍

一、认识多任务、多进程、单线程、多线程

要认识多线程就要从操作系统的原理说起。

 

以前古老的DOS操作系统(V 6.22)是单任务的,还没有线程的概念,系统在每次只能做一件事情。比如你在copy东西的时候不能rename文件名。为了提高系统的利用效率,采用批处理来批量执行任务。

 

现在的操作系统都是多任务操作系统,每个运行的任务就是操作系统所做的一件事情,比如你在听歌的同时还在用MSN和好友聊天。听歌和聊天就是两个任务,这个两个任务是“同时”进行的。一个任务一般对应一个进程,也可能包含好几个进程。比如运行的MSN就对应一个MSN的进程,如果你用的是windows系统,你就可以在任务管理器中看到操作系统正在运行的进程信息。

 

一般来说,当运行一个应用程序的时候,就启动了一个进程,当然有些会启动多个进程。启动进程的时候,操作系统会为进程分配资源,其中最主要的资源是内存空间,因为程序是在内存中运行的。在进程中,有些程序流程块是可以乱序执行的,并且这个代码块可以同时被多次执行。实际上,这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行的时候,这样的执行模式成为并发执行。

 

多线程的目的是为了最大限度的利用CPU资源。

 

Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。

 

一般常见的Java应用程序都是单线程的。比如,用java命令运行一个最简单的HelloWorldJava应用程序时,就启动了一个JVM进程,JVM找到程序程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。当main方法结束后,主线程运行完成。JVM进程也随即退出

 

对于一个进程中的多个线程来说,多个线程共享进程的内存块,当有新的线程产生的时候,操作系统不分配新的内存,而是让新线程共享原有的进程块的内存。因此,线程间的通信很容易,速度也很快。不同的进程因为处于不同的内存块,因此进程之间的通信相对困难。

 

实际上,操作系统的多进程实现了多任务并发执行,程序的多线程实现了进程的并发执行。多任务、多进程、多线程的前提都是要求操作系统提供多任务、多进程、多线程的支持。

 

Java程序中,JVM负责线程的调度。线程调度是值按照特定的机制为多个线程分配CPU的使用权。

调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。

 

所谓的“并发执行”、“同时”其实都不是真正意义上的“同时”。众所周知,CPU都有个时钟频率,表示每秒中能执行cpu指令的次数。在每个时钟周期内,CPU实际上只能去执行一条(也有可能多条)指令。操作系统将进程线程进行管理,轮流(没有固定的顺序)分配每个进程很短的一段是时间(不一定是均分),然后在每个线程内部,程序代码自己处理该进程内部线程的时间分配,多个线程之间相互的切换去执行,这个切换时间也是非常短的。因此多任务、多进程、多线程都是操作系统给人的一种宏观感受,从微观角度看,程序的运行是异步执行的。

 

用一句话做总结:虽然操作系统是多线程的,但CPU每一时刻只能做一件事,和人的大脑是一样的,呵呵。

 

 

二、Java与多线程

 

Java语言的多线程需要操作系统的支持。

 

Java 虚拟机允许应用程序并发地运行多个执行线程。Java语言提供了多线程编程的扩展点,并给出了功能强大的线程控制API

 

Java中,多线程的实现有两种方式:

扩展java.lang.Thread

实现java.lang.Runnable接口

 

 

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。

 

Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

 

调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。

非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。

 

 

三、扩展java.lang.Thread

 

/**

 * File Name:   TestMitiThread.java

 * Created by:  IntelliJ IDEA.

 * Copyright:   Copyright (c) 2003-2006

 * Company:     Lavasoft( [url]http://lavasoft.blog.51cto.com/[/url])

 * Author:      leizhimin

 * Modifier:    leizhimin

 * Date Time:   2007-5-17 10:03:12

 * Readme:      通过扩展Thread类实现多线程

 */

public class TestMitiThread {

    public static void main(String[] rags) {

        System.out.println(Thread.currentThread().getName() + " 线程运行开始!");

        new MitiSay("A").start();

        new MitiSay("B").start();

        System.out.println(Thread.currentThread().getName() + " 线程运行结束!");

    }

}

 

class MitiSay extends Thread {

    public MitiSay(String threadName) {

        super(threadName);

    }

 

    public void run() {

        System.out.println(getName() + " 线程运行开始!");

        for (int i = 0; i < 10; i++) {

            System.out.println(i + " " + getName());

            try {

                sleep((int) Math.random() * 10);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        System.out.println(getName() + " 线程运行结束!");

    }

}

 

运行结果:

 

main 线程运行开始!

main 线程运行结束!

A 线程运行开始!

0 A

1 A

B 线程运行开始!

2 A

0 B

3 A

4 A

1 B

5 A

6 A

7 A

8 A

9 A

A 线程运行结束!

2 B

3 B

4 B

5 B

6 B

7 B

8 B

9 B

B 线程运行结束!

 

说明:

程序启动运行main时候,java虚拟机启动一个进程,主线程mainmain()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。

 

在一个方法中调用Thread.currentThread().getName()方法,可以获取当前线程的名字。在mian方法中调用该方法,获取的是主线程的名字。

 

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。

从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。

Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。

实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

 

 

四、实现java.lang.Runnable接口

 

/**

 * 通过实现 Runnable 接口实现多线程

 */

public class TestMitiThread1 implements Runnable {

 

    public static void main(String[] args) {

        System.out.println(Thread.currentThread().getName() + " 线程运行开始!");

        TestMitiThread1 test = new TestMitiThread1();

        Thread thread1 = new Thread(test);

        Thread thread2 = new Thread(test);

        thread1.start();

        thread2.start();

        System.out.println(Thread.currentThread().getName() + " 线程运行结束!");

    }

 

    public void run() {

        System.out.println(Thread.currentThread().getName() + " 线程运行开始!");

        for (int i = 0; i < 10; i++) {

            System.out.println(i + " " + Thread.currentThread().getName());

            try {

                Thread.sleep((int) Math.random() * 10);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        System.out.println(Thread.currentThread().getName() + " 线程运行结束!");

    }

}

 

运行结果:

 

main 线程运行开始!

Thread-0 线程运行开始!

main 线程运行结束!

0 Thread-0

Thread-1 线程运行开始!

0 Thread-1

1 Thread-1

1 Thread-0

2 Thread-0

2 Thread-1

3 Thread-0

3 Thread-1

4 Thread-0

4 Thread-1

5 Thread-0

6 Thread-0

5 Thread-1

7 Thread-0

8 Thread-0

6 Thread-1

9 Thread-0

7 Thread-1

Thread-0 线程运行结束!

8 Thread-1

9 Thread-1

Thread-1 线程运行结束!

 

说明:

TestMitiThread1类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Threadstart()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

 

五、读解ThreadAPI

 

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.stopThread.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.stopThread.suspend Thread.resume?》。

 void stop(Throwable obj)

          已过时。 该方法具有固有的不安全性。请参阅 stop() 以获得详细信息。该方法的附加危险是它可用于生成目标线程未准备处理的异常(包括若没有该方法该线程不太可能抛出的已检查的异常)。有关更多信息,请参阅为何 Thread.stopThread.suspend Thread.resume 遭到反对?。

 void suspend()

          已过时。 该方法已经遭到反对,因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用 resume 之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。有关更多信息,请参阅为何 Thread.stopThread.suspend Thread.resume 遭到反对?。

 String toString()

          返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

static void yield()

          暂停当前正在执行的线程对象,并执行其他线程。

 

 

六、线程的状态转换图

 

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

<!--[if gte vml 1]><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"> <v:stroke joinstyle="miter" /> <v:formulas> <v:f eqn="if lineDrawn pixelLineWidth 0" /> <v:f eqn="sum @0 1 0" /> <v:f eqn="sum 0 0 @1" /> <v:f eqn="prod @2 1 2" /> <v:f eqn="prod @3 21600 pixelWidth" /> <v:f eqn="prod @3 21600 pixelHeight" /> <v:f eqn="sum @0 0 1" /> <v:f eqn="prod @6 1 2" /> <v:f eqn="prod @7 21600 pixelWidth" /> <v:f eqn="sum @8 21600 0" /> <v:f eqn="prod @7 21600 pixelHeight" /> <v:f eqn="sum @10 21600 0" /> </v:formulas> <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect" /> <o:lock v:ext="edit" aspectratio="t" /> </v:shapetype><v:shape id="图片_x0020_6" o:spid="_x0000_i1028" type="#_x0000_t75" alt="说明: http://img1.51cto.com/attachment/200705/200705181179465004843.png" style='width:395.45pt;height:281.35pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:\Users\blueram\AppData\Local\Temp\msohtmlclip1\01\clip_image001.png" o:title="200705181179465004843" /> </v:shape><![endif]--><!--[if !vml]-->说明: http://img1.51cto.com/attachment/200705/200705181179465004843.png<!--[endif]-->

 

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

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的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~10Thread类有以下三个静态常量:

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具有相同的优先级。

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(),唤醒在此对象监视器上等待的所有线程。

 

注意:Threadsuspend()resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

 

7、常见线程名词解释

主线程:JVM调用程序mian()所产生的线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。

后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。

前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()setDaemon()方法来判断和设置一个线程是否为后台线程。

 

C++的多进程是OS系统并发的一个任务

Java中没有多进程,一个JVM就是一个进程

==========================================

JVM             Thread 对象

类似于代理模式,通过操作Thread 间接访问线程。 

==========================================

OS                  线程

==========================================

线程是在进程中并发的一个顺序的执行流程

 

多进程:划分时间片,宏观上并行,微观上串行

多线程:cpu在进程内部再划分时间片

 

CPU ,代码 ,数据

进程:进程间数据独立

线程:数据空间共享,堆空间的共享(堆空间中存放的是对象),栈空间是独立的

所以线程间切换容易,称为轻量级进程

 

一个线程对象代表了一个线程,并非就是一个线程

线程是操作系统中负责维护的资源

java.lang.Thread类的一个对象就代表一个线程

线程是底层OS维护的资源,JVM跑在OS上,在JVM中创建一个Thread对象,调用其start()方法,底层OS会申请一个线程资源,线程对象可到底层管理一个线程创建好线程之后,把要让线程执行的代码封装到线程对象中(覆盖run()方法)

 

线程状态

线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。用一个图来描述如下:

<!--[if gte vml 1]><v:shape id="图片_x0020_5" o:spid="_x0000_i1027" type="#_x0000_t75" alt="说明: http://img1.51cto.com/attachment/200809/200809131221320062031.png" style='width:391.55pt;height:202.7pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:\Users\blueram\AppData\Local\Temp\msohtmlclip1\01\clip_image003.png" o:title="200809131221320062031" /> </v:shape><![endif]--><!--[if !vml]-->说明: http://img1.51cto.com/attachment/200809/200809131221320062031.png<!--[endif]-->

1、新状态:线程对象已经创建,还没有在其上调用start()方法。

 

2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

 

3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

 

4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

 

5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

 

有关详细状态转换图可以参看本人的Java多线程编程总结中的图

 

二、阻止线程执行

对于线程的阻止,考虑一下三个方面,不考虑IO阻塞的情况:

睡眠;

等待;

因为需要一个对象的锁定而被阻塞。

 

1、睡眠

Thread.sleep(long millis)Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以减慢线程。当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。

 

线程睡眠的原因:线程执行太快,或者需要强制进入下一轮,因为Java规范不保证合理的轮换。

 

睡眠的实现:调用静态方法。

        try {
            Thread.sleep(123);
        } catch (InterruptedException e) {
            e.printStackTrace();  
        }

 

睡眠的位置:为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠。

 

例如,在前面的例子中,将一个耗时的操作改为睡眠,以减慢线程的执行。可以这么写:

 

    public void run() {
        for(int i = 0;i<5;i++){

// 很耗时的操作,用来减慢线程的执行
//            for(long k= 0; k <100000000;k++);
            try {
                Thread.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();  .
            }

            System.out.println(this.getName()+" :"+i);
        }
    }

 

运行结果:

阿三 :0 
李四 :0 
阿三 :1 
阿三 :2 
阿三 :3 
李四 :1 
李四 :2 
阿三 :4 
李四 :3 
李四 :4 

Process finished with exit code 0

 

这样,线程在每次执行过程中,总会睡眠3毫秒,睡眠了,其他的线程就有机会执行了。

 

注意:

1、线程睡眠是帮助所有线程获得运行机会的最好方法。

2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

3sleep()是静态方法,只能控制当前正在运行的线程。

 

下面给个例子:

/** 
*
一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串 

* @author leizhimin 2008-9-14 9:53:49 
*/ 
public class MyThread extends Thread { 

    public void run() { 
        for (int i = 0; i < 100; i++) { 
            if ((i) % 10 == 0) { 
                System.out.println("-------" + i); 
            } 
            System.out.print(i); 
            try { 
                Thread.sleep(1); 
                System.out.print("    
线程睡眠1毫秒!\n"); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
        } 
    } 

    public static void main(String[] args) { 
        new MyThread().start(); 
    } 

 

-------0 
0    
线程睡眠1毫秒! 
1    
线程睡眠1毫秒! 
2    
线程睡眠1毫秒! 
3    
线程睡眠1毫秒! 
4    
线程睡眠1毫秒! 
5    
线程睡眠1毫秒! 
6    
线程睡眠1毫秒! 
7    
线程睡眠1毫秒! 
8    
线程睡眠1毫秒! 
9    
线程睡眠1毫秒! 
-------10 
10    
线程睡眠1毫秒! 
11    
线程睡眠1毫秒! 
12    
线程睡眠1毫秒! 
13    
线程睡眠1毫秒! 
14    
线程睡眠1毫秒! 
15    
线程睡眠1毫秒! 
16    
线程睡眠1毫秒! 
17    
线程睡眠1毫秒! 
18    
线程睡眠1毫秒! 
19    
线程睡眠1毫秒! 
-------20 
20    
线程睡眠1毫秒! 
21    
线程睡眠1毫秒! 
22    
线程睡眠1毫秒! 
23    
线程睡眠1毫秒! 
24    
线程睡眠1毫秒! 
25    
线程睡眠1毫秒! 
26    
线程睡眠1毫秒! 
27    
线程睡眠1毫秒! 
28    
线程睡眠1毫秒! 
29    
线程睡眠1毫秒! 
-------30 
30    
线程睡眠1毫秒! 
31    
线程睡眠1毫秒! 
32    
线程睡眠1毫秒! 
33    
线程睡眠1毫秒! 
34    
线程睡眠1毫秒! 
35    
线程睡眠1毫秒! 
36    
线程睡眠1毫秒! 
37    
线程睡眠1毫秒! 
38    
线程睡眠1毫秒! 
39    
线程睡眠1毫秒! 
-------40 
40    
线程睡眠1毫秒! 
41    
线程睡眠1毫秒! 
42    
线程睡眠1毫秒! 
43    
线程睡眠1毫秒! 
44    
线程睡眠1毫秒! 
45    
线程睡眠1毫秒! 
46    
线程睡眠1毫秒! 
47    
线程睡眠1毫秒! 
48    
线程睡眠1毫秒! 
49    
线程睡眠1毫秒! 
-------50 
50    
线程睡眠1毫秒! 
51    
线程睡眠1毫秒! 
52    
线程睡眠1毫秒! 
53    
线程睡眠1毫秒! 
54    
线程睡眠1毫秒! 
55    
线程睡眠1毫秒! 
56    
线程睡眠1毫秒! 
57    
线程睡眠1毫秒! 
58    
线程睡眠1毫秒! 
59    
线程睡眠1毫秒! 
-------60 
60    
线程睡眠1毫秒! 
61    
线程睡眠1毫秒! 
62    
线程睡眠1毫秒! 
63    
线程睡眠1毫秒! 
64    
线程睡眠1毫秒! 
65    
线程睡眠1毫秒! 
66    
线程睡眠1毫秒! 
67    
线程睡眠1毫秒! 
68    
线程睡眠1毫秒! 
69    
线程睡眠1毫秒! 
-------70 
70    
线程睡眠1毫秒! 
71    
线程睡眠1毫秒! 
72    
线程睡眠1毫秒! 
73    
线程睡眠1毫秒! 
74    
线程睡眠1毫秒! 
75    
线程睡眠1毫秒! 
76    
线程睡眠1毫秒! 
77    
线程睡眠1毫秒! 
78    
线程睡眠1毫秒! 
79    
线程睡眠1毫秒! 
-------80 
80    
线程睡眠1毫秒! 
81    
线程睡眠1毫秒! 
82    
线程睡眠1毫秒! 
83    
线程睡眠1毫秒! 
84    
线程睡眠1毫秒! 
85    
线程睡眠1毫秒! 
86    
线程睡眠1毫秒! 
87    
线程睡眠1毫秒! 
88    
线程睡眠1毫秒! 
89    
线程睡眠1毫秒! 
-------90 
90    
线程睡眠1毫秒! 
91    
线程睡眠1毫秒! 
92    
线程睡眠1毫秒! 
93    
线程睡眠1毫秒! 
94    
线程睡眠1毫秒! 
95    
线程睡眠1毫秒! 
96    
线程睡眠1毫秒! 
97    
线程睡眠1毫秒! 
98    
线程睡眠1毫秒! 
99    
线程睡眠1毫秒! 

Process finished with exit code 0 


2
、线程的优先级和线程让步yield()

线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。

 

要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。

 

注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。

 

当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。

 

设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:

        Thread t = new MyThread();
        t.setPriority(8);
        t.start();

线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。

 

线程默认优先级是5Thread类中有三个常量,定义线程优先级范围:

static int MAX_PRIORITY 
         
线程可以具有的最高优先级。 
static int MIN_PRIORITY 
         
线程可以具有的最低优先级。 
static int NORM_PRIORITY 
         
分配给线程的默认优先级。

 

3Thread.yield()方法

 

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

 

4join()方法

 

Thread的非静态方法join()让一个线程B“加入到另外一个线程A的尾部。在A执行完毕之前,B不能工作。例如:

        Thread t = new MyThread();
        t.start();
        t.join();

另外,join()方法还有带超时限制的重载版本。 例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。

 

线程的加入join()对线程栈导致的结果是线程栈发生了变化,当然这些变化都是瞬时的。下面给示意图:

 

<!--[if gte vml 1]><v:shape id="图片_x0020_4" o:spid="_x0000_i1026" type="#_x0000_t75" alt="说明: http://img1.51cto.com/attachment/200809/200809141221361241015.png" style='width:382.7pt;height:306.3pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:\Users\blueram\AppData\Local\Temp\msohtmlclip1\01\clip_image005.png" o:title="200809141221361241015" /> </v:shape><![endif]--><!--[if !vml]-->说明: http://img1.51cto.com/attachment/200809/200809141221361241015.png<!--[endif]-->

 

 

小结

到目前位置,介绍了线程离开运行状态的3种方法:

1、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。

2、调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。

3、调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

 

除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:

1、线程的run()方法完成。

2、在对象上调用wait()方法(不是在线程上调用)。

3、线程不能在对象上获得锁定,它正试图运行该对象的方法代码。

4、线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。

 

线程的实现方式

java中要想实现多线程,一般认为有两种手段,一种是继续Thread类,另外一种是实现Runable接口;但是还有带有返回值Callable实现。

 

/**

 *线程的4种构造和启动方法

 * @author blueram

 *

 */

public class ThreadConstruct {

 

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        T1 t1 = new T1();

        Thread t = new Thread(t1);

        t.start();

 

        T2 t2 = new T2();

        t2.start();

 

        T3 t3 = new T3();

        FutureTask<String> futureTask = new FutureTask<String>(t3);

        Thread thread3 = new Thread(futureTask);

        thread3.start();

        System.out.println(futureTask.get());

 

        ExecutorService ec = Executors.newSingleThreadExecutor();

        Future<String> fu =  ec.submit(t3);

        ec.submit(t1);

        ec.shutdown();

        System.out.println(fu.get());

    }

}

//实现Runnable接口

class T1 implements Runnable {

    public void run() {

        // TODO Auto-generated method stub

        System.out.println("t1 is started");

    }

 

}

//继承thread

class T2 extends Thread{

    @Override

    public void run() {

        try {

             TimeUnit.SECONDS.sleep(1);

             Thread.currentThread().sleep(1000);

        } catch (InterruptedException e) {

             // TODO Auto-generated catch block

             e.printStackTrace();

        }

        System.out.println("t2 is started");

    }

}

//返回值构建

class T3 implements Callable<String>{

 

    public String call() throws Exception {

        return "t3 is started";

    }

 

}

线程的加入join

如果有一个线程a正在运行,您希望插入一个线程b,并要求线程b执行完后,再继续执行a,就可以使用join

package cn.blueram.thred.join;

class ThreadJoinDemo implements Runnable {

        public void run() {

             for (int i = 0; i < 3; i++) {

                 System.out.println(Thread.currentThread().getName());

             }

        }

        public static void main(String[] args) {

             System.out.println("main 线程开始");

             ThreadJoinDemo he = new ThreadJoinDemo();

             Thread demo = new Thread(he,"线程");

             demo.start();

             try{

                 //demo.join();  //强制执行demo

             }catch (Exception e) {

                 e.printStackTrace();

             }

             System.out.println("main 线程结束");

        }

    }

线程的daemon

前台线程创建的线程默认是前台线程,如使用thread.setDaemon(true)则设置为后台进程。

后台线程创建的默认是后台线程。

package cn.blueram.thred.daemon;

 

import java.util.concurrent.TimeUnit;

 

public class DaemonThread {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        Thread thread = new Thread(new Runnable(){

             @Override

             public void run() {

                 // TODO Auto-generated method stub

                 System.out.println("thread is start");

                 try {

                     TimeUnit.SECONDS.sleep(5);

                 } catch (InterruptedException e) {

                     e.printStackTrace();

                 }

                 System.out.println("thread is sleep");

             }

        });

        thread.setDaemon(true);

        thread.start();

        System.out.println("main is end");

    }

}

 

线程的停止

使用标示符开控制线程的停止

 shutDown() 

   
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。 

 shutdownNow() 

    
根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。 
    
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep waitCondition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。 

   
上面对shutDown()以及shutDownNow()作了一个简单的、理论上的分析。如果想知道why,则需要亲自打开JDK源码,分析分析。 
     
想要分析shutDown()以及shutDownNow()源码,我建议首先要对ThreadPoolExecutor有个大概了解。因为关闭线程池的所有方法逻辑都在ThreadPoolExecutor中处理的。 
     
如果你真的想知道为什么,建议看一下我以前写的一篇对ThreadPoolExecutor源码分析的博文,我想这对你比较透彻的了解shutDown()shutDownNow()的区别以及java 线程池原理有很大的帮助。

 

package cn.blueram.thred.stop;

 

import java.util.List;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.TimeUnit;

 

public class ThreadStopDemo {

 

          /**

           * 主线程控制子线程的开始和停止

           *

           * @param args

           * @throws InterruptedException

           */

          public static void main(String[] args) throws InterruptedException {

                   // TODO Auto-generated method stub

                   T1 t1 = new T1();

                   System.out.println("普通方式启动线程");

                   Thread thread = new Thread(t1);

                   t1.setFlag(true);

                   thread.start();

                   TimeUnit.SECONDS.sleep(5);

                   t1.setFlag(false);

                   TimeUnit.SECONDS.sleep(1);

                   System.out.println("普通方式启动线程结束");

 

                   //线程池方式

                   System.out.println("线程池启动");

                   t1.setFlag(true);

                   ExecutorService es = Executors.newSingleThreadExecutor();

                   es.submit(t1);

                   TimeUnit.SECONDS.sleep(5);

                   List<Runnable> runs =es.shutdownNow();

                   System.out.println("线程池结束");

                   System.out.println("线程池存活数:"+runs.size());

                   for (Runnable runnable : runs) {

                             System.out.println("current is running thread name :"+runnable.getClass().getName());

                   }

                   t1.setFlag(false);

          }

}

class T1 implements Runnable{

 

          private boolean flag = false;

          public boolean isFlag() {

                   return flag;

          }

          public void setFlag(boolean flag) {

                   this.flag = flag;

          }

          @Override

          public void run() {

                   while(flag){

                             try {

                                      TimeUnit.SECONDS.sleep(1);

                             } catch (InterruptedException e) {

                                      e.printStackTrace();

                             }

                             System.out.println("sub thread is starting");

                   }

          }

 

}

线程的异常处理

package cn.blueram.thred.exception;

 

import java.lang.Thread.UncaughtExceptionHandler;

 

public class ThreadHandlerDemo {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

        ThreadExceptionHandler handler = new ThreadExceptionHandler();

 

        Thread thread = new Thread(new Runnable() {

 

           @Override

           public void run() {

               throw new RuntimeException("thread exception");

           }

        });

 

        //thread.setUncaughtExceptionHandler(handler);

        thread.setName("测试");

        thread.start();

    }

}

class ThreadExceptionHandler implements UncaughtExceptionHandler {

 

    @Override

    public void uncaughtException(Thread t, Throwable e) {

        System.out.println("current thread name:"+t.getName());

        System.out.println("throwable name:"+e.getMessage());

        System.out.println("i hand exception");

    }

}

 

Threadlocal

其实ThreadLocal 跟我们做web开发时使用的session对象的作用很类似,每当我们向服务器发送一个请求时,web服务器会为该请求创建一个线程,同时会为该请求创建一系列对象,其中包括session(当然在同一个浏览器发送的请求都获得是同一个session对象),所以当我们做web开发时,可以不用担心线程安全问题,自由的往session中存取变量,保存用户状态。同理,当我们在程序中第一次使用A.threadLocal.set(user) 存放参数时,不管在程序的哪个地方,我们都可以通过ThreadLocal  所在的接口访问静态threadLocal对象,同时来共享threadLocal存放的参数,threadLocal就相当于session的作用,来保存当前线程的状态。在我们开发实际开发中,可以任意往threadLocal中共享和存取自己需要的变量,只不过web中的session的生命周期在于客户端的浏览器,而threadLocal中存储的变量的生命周期只在于当前线程,当前结束,threadLocal中存放的参数也被销毁。

Spring使用ThreadLocal解决线程安全问题

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolderTransactionSynchronizationManagerLocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:

<!--[if gte vml 1]><v:shape id="图片_x0020_1" o:spid="_x0000_i1025" type="#_x0000_t75" alt="说明: 通通透透理解ThreadLocal" style='width:330.65pt;height:196.6pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:\Users\blueram\AppData\Local\Temp\msohtmlclip1\01\clip_image007.gif" o:title="通通透透理解ThreadLocal" /> </v:shape><![endif]--><!--[if !vml]-->说明: 通通透透理解ThreadLocal<!--[endif]-->

1同一线程贯通三层

这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

下面的实例能够体现Spring对有状态Bean的改造思路:

代码清单3 TopicDao:非线程安全

public class TopicDao {

private Connection conn;一个非线程安全的变量

public void addTopic(){

Statement stat = conn.createStatement();引用非线程安全变量

}

}

由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocalconn这个非线程安全的状态进行改造:

代码清单4 TopicDao:线程安全

import java.sql.Connection;

import java.sql.Statement;

public class TopicDao {

使用ThreadLocal保存Connection变量

private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();

public static Connection getConnection(){

如果connThreadLocal没有本线程对应的Connection创建一个新的Connection

并将其保存到线程本地变量中。

if (connThreadLocal.get() == null) {

Connection conn = ConnectionManager.getConnection();

connThreadLocal.set(conn);

return conn;

}else{

return connThreadLocal.get();直接返回线程本地变量

}

}

public void addTopic() {

ThreadLocal中获取线程对应的Connection

Statement stat = getConnection().createStatement();

}

}

不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

当然,这个例子本身很粗糙,将ConnectionThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection

小结

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

 

同步化

数据在多线程下共享时,由于同时多个线程可能更新同一个对象的信息,易造成对象数据的不同步。并且非常不易察觉,有时几年后才会发现。

这里引进对象的锁定观念。每个对象在内部都会有一个锁定。对象的锁定在平时时没有作用的。被标示为synchronized的方法会成为同步区域,当线程执行某个对象的同步区域时,对象的锁定就有作用了。要执行同步区的线程,都必须先获得对象的锁定,执行完同步区域之后再将锁定归还给对象。

因为对象的锁定只有一个,当有一个线程已取走锁定而在执行同步区域中的程序代码时,若其他线程也想执行synchronized的区域,但这些线程无法获得锁定,所以只好在对象的锁定池等待,直到锁定被前一个线程归还为止,此时在锁定池的线程竞争被归还的对象锁定,只有锁定的线程才能进入runnable状态。

Java Executors(线程池)

 

        SunJava5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利。为了编写高效稳定可靠的多线程程序,线程部分的新增内容显得尤为重要。 

  有关Java5线程新特征的内容全部在java.util.concurrent下面,里面包含数目众多的接口和类,熟悉这部分API特征是一项艰难的学习过程。目前有关这方面的资料和书籍都少之又少,大所属介绍线程方面书籍还停留在java5之前的知识层面上。 

  当然新特征对做多线程程序没有必须的关系,在java5之前通用可以写出很优秀的多线程程序。只是代价不一样而已。 

  线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。 

  在Java5之前,要实现一个线程池是相当有难度的,现在Java5为我们做好了一切,我们只需要按照提供的API来使用,即可享受线程池带来的极大便利。 

  Java5的线程池分好多种:具体的可以分为两类,固定尺寸的线程池、可变尺寸连接池。 

  在使用线程池之前,必须知道如何去创建一个线程池,在Java5中,需要了解的是java.util.concurrent.Executors类的API,这个类提供大量创建连接池的静态方法,是必须掌握的。 

一、固定大小的线程池,newFixedThreadPool

[java] view plaincopy

  1. package app.executors;  
  2.   
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.ExecutorService;  
  5.   
  6. /** 
  7.  * Java线程:线程池 
  8.  *  
  9.  * @author 冯小卫 
  10.  */  
  11. public class Test {  
  12.     public static void main(String[] args) {  
  13.         // 创建一个可重用固定线程数的线程池  
  14.         ExecutorService pool = Executors.newFixedThreadPool(5);  
  15.         // 创建线程  
  16.         Thread t1 = new MyThread();  
  17.         Thread t2 = new MyThread();  
  18.         Thread t3 = new MyThread();  
  19.         Thread t4 = new MyThread();  
  20.         Thread t5 = new MyThread();  
  21.         // 将线程放入池中进行执行  
  22.         pool.execute(t1);  
  23.         pool.execute(t2);  
  24.         pool.execute(t3);  
  25.         pool.execute(t4);  
  26.         pool.execute(t5);  
  27.         // 关闭线程池  
  28.         pool.shutdown();  
  29.     }  
  30. }  
  31.   
  32. class MyThread extends Thread {  
  33.     @Override  
  34.     public void run() {  
  35.         System.out.println(Thread.currentThread().getName() + "正在执行。。。");  
  36.     }  
  37. }  


输出结果:

[html] view plaincopy

  1. pool-1-thread-1正在执行。。。  
  2. pool-1-thread-3正在执行。。。  
  3. pool-1-thread-4正在执行。。。  
  4. pool-1-thread-2正在执行。。。  
  5. pool-1-thread-5正在执行。。。  


改变ExecutorService pool = Executors.newFixedThreadPool(5)中的参数:ExecutorService pool = Executors.newFixedThreadPool(2),输出结果是:

[html] view plaincopy

  1. pool-1-thread-1正在执行。。。  
  2. pool-1-thread-1正在执行。。。  
  3. pool-1-thread-2正在执行。。。  
  4. pool-1-thread-1正在执行。。。  
  5. pool-1-thread-2正在执行。。。  


从以上结果可以看出,newFixedThreadPool的参数指定了可以运行的线程的最大数目,超过这个数目的线程加进去以后,不会运行。其次,加入线程池的线程属于托管状态,线程的运行不受加入顺序的影响。

 

二、单任务线程池,newSingleThreadExecutor

仅仅是把上述代码中的ExecutorService pool = Executors.newFixedThreadPool(2)改为ExecutorService pool = Executors.newSingleThreadExecutor();

输出结果:

[html] view plaincopy

  1. pool-1-thread-1正在执行。。。  
  2. pool-1-thread-1正在执行。。。  
  3. pool-1-thread-1正在执行。。。  
  4. pool-1-thread-1正在执行。。。  
  5. pool-1-thread-1正在执行。。。  

可以看出,每次调用execute方法,其实最后都是调用了thread-1run方法。

 

三、可变尺寸的线程池,newCachedThreadPool

与上面的类似,只是改动下pool的创建方式:ExecutorService pool = Executors.newCachedThreadPool();


输出:

[html] view plaincopy

  1. pool-1-thread-1正在执行。。。  
  2. pool-1-thread-2正在执行。。。  
  3. pool-1-thread-4正在执行。。。  
  4. pool-1-thread-3正在执行。。。  
  5. pool-1-thread-5正在执行。。。  


这种方式的特点是:可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

四、延迟连接池,newScheduledThreadPool

[java] view plaincopy

  1. package app.executors;  
  2.   
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.ScheduledExecutorService;  
  5. import java.util.concurrent.TimeUnit;  
  6.   
  7. /** 
  8.  * Java线程:线程池 
  9.  *  
  10.  * @author 冯小卫 
  11.  */  
  12. public class Test {  
  13.     public static void main(String[] args) {  
  14.         // 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。  
  15.         ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);  
  16.         // 创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口  
  17.         Thread t1 = new MyThread();  
  18.         Thread t2 = new MyThread();  
  19.         Thread t3 = new MyThread();  
  20.         // 将线程放入池中进行执行  
  21.         pool.execute(t1);  
  22.         // 使用延迟执行风格的方法  
  23.         pool.schedule(t2, 1000, TimeUnit.MILLISECONDS);  
  24.         pool.schedule(t3, 10, TimeUnit.MILLISECONDS);  
  25.   
  26.         // 关闭线程池  
  27.         pool.shutdown();  
  28.     }  
  29. }  
  30.   
  31. class MyThread extends Thread {  
  32.     @Override  
  33.     public void run() {  
  34.         System.out.println(Thread.currentThread().getName() + "正在执行。。。");  
  35.     }  
  36. }  


读者可以尝试改变Executors.newScheduledThreadPool(2)的参数来得到更多的体验,当然,让

[java] view plaincopy

  1. @Override  
  2. public void run() {  
  3.     System.out.println(Thread.currentThread().getName() + "正在执行。。。");  
  4. }  

 

变成一个无限循环,你可以得到更多的关于pool.shutdown()的用法。

 

五:单任务延迟连接池(和上面类似,就不写了)。当然我们也可以自定义线程池,这里就不写了,累啊……

 

 

 

java多线程备忘

Java多线程编程总结 *** 
http://lavasoft.blog.51cto.com/62575/27069/ 

Java
多线程总结之聊一聊Queue 
http://hellosure.iteye.com/blog/1126541 

Java
多线程总结之由synchronized说开去 
http://hellosure.iteye.com/blog/1121157 

ConcurrentHashMap为例小议并发集合类 
http://hellosure.iteye.com/blog/1143942 

Java
并发编程之ConcurrentHashMap 
http://www.iteye.com/topic/1103980 

Java5 Concurrent
包中的ConcurrentHashMap 
http://www.iteye.com/topic/333669 

Java
synchronized关键字:同步机制总结 
http://hellosure.iteye.com/blog/948549 

Java:
使用Executors创建和管理线程 
http://hellosure.iteye.com/blog/947045 

Java5
多线程实践 
http://javahy.iteye.com/blog/384466 

淘宝面试题:如何充分利用多核CPU,计算很大的List中所有整数的和 
http://www.iteye.com/topic/711162 

Java
线程池学习  
http://www.cnblogs.com/jersey/archive/2011/03/30/2000231.html 

java
并发编程-Executor框架 
http://www.iteye.com/topic/366591 

java
并发编程-构建块 
http://blogclosed.iteye.com/blog/366777 

java
并发编程-Executor框架 
http://blogclosed.iteye.com/blog/366591 

Java5
多线程实践 
http://www.ibm.com/developerworks/cn/java/j-zhanghz/ 

实战Concurrent 
http://www.iteye.com/topic/363625 

JDK5
中的concurrent包、线程池 
http://jiajun.iteye.com/blog/910828 

java
线程池 较详细文摘 
http://blog.csdn.net/ichsonx/article/details/6265071 

ExecutorService
executesubmit方法 
http://blog.csdn.net/peachpi/article/details/6771946 


ThreadPoolExecutor
使用和思考()-线程池大小设置与BlockingQueue的三种实现区别    
http://dongxuan.iteye.com/blog/901689 

ThreadPoolExecutor
使用和思考()-keepAliveTime及拒绝策略 
http://dongxuan.iteye.com/blog/902571 

监控客户端设计-记录-输出部分 
http://dongxuan.iteye.com/blog/875764 

监控服务端设计-SSH-日志抓取写入部分 
http://dongxuan.iteye.com/blog/876423 

ThreadPoolExecutor
使用介绍 
http://blog.csdn.net/wangwenhui11/article/details/6760474 

守护线程总结 
http://coach.iteye.com/blog/854714 

关注QueueJava 1.5 添加新的数据结构接口 
http://blog.csdn.net/Victor_Jan/article/details/117695 

精巧好用的DelayQueue 
http://www.blogjava.net/jobs/archive/2007/04/27/114193.html 

也谈大规模定时器的实时集中管理实现 
http://bbs.51cto.com/thread-441318-1-1.html 

Java
理论与实践: 正确使用 Volatile 变量 
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html 

Java1.5
多线程新特性 Concurrent包分析 
http://www.cnblogs.com/xitang/archive/2011/03/12/1982179.html 

如何聪明地使用锁 
http://www.ibm.com/developerworks/cn/java/j-lo-lock/index.html?S_TACT=105AGX52&S_CMP=tec-yesky

 

分享到:
评论

相关推荐

    Java多线程知识点总结

    Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...

    Java多线程的总结

    Java多线程是Java编程中的一个核心概念,它在现代软件开发中扮演着至关重要的角色。多线程允许程序同时执行多个任务,提高了系统资源的利用率,提升了应用程序的响应速度和并发性能。对于大型分布式系统、Web应用...

    java多线程总结(一)

    Java多线程是Java编程语言中的一个重要特性,它允许开发者创建并发执行的多个线程,从而提高程序的执行效率和响应速度。Java中实现多线程主要有两种方式:继承Thread类和实现Runnable接口。 ### 继承Thread类 在...

    Java多线程编程总结

    ### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...

    java多线程编程总结

    ### Java多线程编程总结 #### 一、Java线程:概念与原理 - **操作系统中线程和进程的概念** 当前的操作系统通常都是多任务操作系统,多线程是一种实现多任务的方式之一。在操作系统层面,进程指的是内存中运行的...

    Java_多线程与并发编程总结.doc

    Java多线程与并发编程是Java开发中至关重要的一部分,它涉及到如何高效地利用CPU资源,以实现程序的并行执行。在操作系统层面,多任务和多进程是通过分配不同的内存空间来实现的,而线程则共享同一进程的内存,这...

    java多线程全面总结

    java多线程全面总结,简单的介绍多线程技术中的各种应用问题,是你对多线程有更多的认识!

    javad多线程

    Java 多线程编程是指在一个程序中可以运行多个线程,以提高程序的执行效率和响应速度。在 Java 中,多线程编程可以通过 Thread 类和 Runnable 接口来实现。 为什么需要多线程? 在单线程程序中,如果某个任务需要...

    JAVA多线程总结

    【JAVA多线程总结】 Java 多线程是Java编程中的关键特性,它允许程序同时执行多个任务,提高系统的效率和响应性。本篇总结涵盖了Java多线程的基础概念、创建与启动、线程调度、同步与协作以及新特性。 **一、Java...

    java多线程分页查询

    ### Java多线程分页查询知识点详解 #### 一、背景与需求分析 在实际的软件开发过程中,尤其是在处理大量数据时,如何高效地进行数据查询成为了一个关键问题。例如,在一个用户众多的社交平台上,当用户需要查看...

    java Socket 多线程

    Java Socket 多线程是网络编程中的一个重要概念,它结合了Java的并发处理能力和Socket通信技术,使得服务器能够同时处理多个客户端的连接请求。在Java中,Socket是用于在网络环境中进行双向通信的类,而多线程则允许...

    40个Java多线程问题总结

    ### Java多线程问题总结 #### 一、多线程的作用与优势 1. **发挥多核CPU的优势:** - 当今计算机硬件普遍配备有多核CPU,利用多线程技术能够有效地分配任务到不同的核心上,使得计算资源得到最大化利用。在双核...

    JAVA多线程(精典总结)

    总结一下,Java多线程涉及的内容广泛,包括线程的基本概念、创建、状态转换、调度和优先级管理。理解并掌握这些知识点对于编写高效并发的Java程序至关重要,也是面试中必不可少的技术点。在实际编程中,合理利用多...

    java多线程之赛马程序实验8多线程练习下载进度

    总结来说,这个实验旨在帮助开发者深入理解Java多线程的概念,熟练运用`Thread`类的`run`和`start`方法,以及如何通过进度条来实时展示多线程任务的执行进度。通过实践,开发者可以更好地掌握并发编程的技巧,这对于...

    JAVA多线程编程技术PDF

    总结起来,“JAVA多线程编程技术PDF”涵盖了多线程的基本概念、同步机制、线程通信、死锁避免、线程池以及线程安全的集合类等内容。通过深入学习这份资料,开发者可以全面掌握Java多线程编程技术,提升程序的并发...

    我总结的Java多线程程序设计

    Java多线程程序设计是Java开发中的重要组成部分,它允许程序在同一时间执行多个任务,从而提高了系统的效率和响应性。本文将深入探讨Java多线程的相关概念和实现方式。 一、理解多线程 1. **线程定义**:线程是一...

    武汉理工大学Java多线程实验源码

    总结来说,这个实验源码涵盖了Java多线程的基础和应用,包括线程的创建、运行、同步、通信,以及网络编程和数据库操作。通过这些实验,学生可以深入理解Java并发编程的核心概念,并掌握实际开发中的多线程设计技巧。

Global site tag (gtag.js) - Google Analytics