多线程基础

一、线程的两种实现方式

Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法:

举一个实际例子来说明:假设一个影院有三个售票口,分别用于向儿童、成人和老人售票。影院为每个窗口放有100张电影票,分别是儿童票、成人票和老人票。三个窗口需要同时卖票,而现在只有一个售票员,这个售票员就相当于一个CPU,三个窗口就相当于三个线程。

1、通过扩展Thread类来创建多线程

  1. 定义一个Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。
  2. 创建Thread子类的实例,即创建了线程对象。
  3. 调用线程对象的start()方法来启动该线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MutliThreadDemo {
public static void main(String[] args) {
MyThread m1=new MyThread("Window 1");
MyThread m2=new MyThread("Window 2");
MyThread m3=new MyThread("Window 3");
m1.start();
m2.start();
m3.start();
}
}

class MyThread extends Thread{
private int ticket=100;//每个线程都拥有100张票
MyThread(String name){
super(name);//调用父类带参数的构造方法
}
public void run(){
while(ticket>0){
System.out.println(ticket--+" is saled by "+Thread.currentThread().getName());
}
}
}

程序中定义一个线程类,它扩展了Thread类。利用扩展的线程类在MutliThreadDemo类的主方法中创建了三个线程对象,并通过start()方法分别将它们启动。

从结果可以看到,每个线程分别对应100张电影票,之间并无任何关系,这就说明每个线程之间是平等的,没有优先级关系,因此都有机会得到CPU的处理。但是结果显示这三个线程并不是依次交替执行,而是在三个线程同时被执行的情况下,有的线程被分配时间片的机会多,票被提前卖完,而有的线程被分配时间片的机会比较少,票迟一些卖完。

可见,利用扩展Thread类创建的多个线程,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰

2、通过实现Runnable接口来创建多线程

  1. 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动该线程。

1.1例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MutliThreadDemo {
public static void main(String[] args) {
MyThread2 m1=new MyThread2();
MyThread2 m2=new MyThread2();
MyThread2 m3=new MyThread2();
Thread t1=new Thread(m1, "Window 1");
Thread t2=new Thread(m2, "Window 2");
Thread t3=new Thread(m3, "Window 3");
t1.start();
t2.start();
t3.start();
}
}

class MyThread2 implements Runnable{
private int ticket=100;//每个线程都拥有100张票
public void run(){
while(ticket>0){
//如果要访问当前线程,则必须使用Thread.currentThread()方法。
System.out.println(ticket--+" is saled by "+Thread.currentThread().getName());
}
}
}

由于这三个线程也是彼此独立,各自拥有自己的资源,即100张电影票,因此程序输出的结果和(1)结果大同小异。均是各自线程对自己的100张票进行单独的处理,互不影响。

可见,只要现实的情况要求保证新建线程彼此相互独立,各自拥有资源,且互不干扰,采用哪个方式来创建多线程都是可以的。因为这两种方式创建的多线程程序能够实现相同的功能。

1.2、例子2

现实中也存在这样的情况,比如模拟一个火车站的售票系统,假如当日从A地发往B地的火车票只有100张,且允许所有窗口卖这100张票,那么每一个窗口也相当于一个线程,但是这时和前面的例子不同之处就在于所有线程处理的资源是同一个资源,即100张车票。如果还用前面的方式来创建线程显然是无法实现的,这种情况该怎样处理呢?看下面这个程序,程序代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MutliThreadDemo {
public static void main(String[] args) {
MyThread3 m1=new MyThread3();
Thread t1=new Thread(m1, "Window 1");
Thread t2=new Thread(m1, "Window 2");
Thread t3=new Thread(m1, "Window 3");
t1.start();
t2.start();
t3.start();
}
}

class MyThread3 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(ticket>0){
System.out.println(ticket--+" is saled by "+Thread.currentThread().getName());
}
}
}

结果正如前面分析的那样,程序在内存中仅创建了一个资源,而新建的三个线程都是基于访问这同一资源的,并且由于每个线程上所运行的是相同的代码,因此它们执行的功能也是相同的。

3、Thread与Runnable

通过上面例子的对比,可以看出两种方法都可以创建新的线程,但是后者与前者相比有着更多的优势:

  1. 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
  2. 多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  3. 几乎所有的多线程程序都是通过实现Runnable接口的方式来完成的。

4、Callable

除了以上两种方法,还有一个叫做Callable的接口,它和Runnable很相似,但也有几点不同:

  1. Callable需要实现call方法,而Runnable需要实现run方法
  2. Callable方法可以返回任意类型的对象
  3. Callable能够抛出异常,而runnable不能
  4. Callable和Runnable都可以应用于executors。而Thread类只支持Runnable.
  5. Callable与executors联合在一起,在任务完成时可立刻获得一个更新了的Future。而Runable却要自己处理

Future接口:一般都是取回Callable执行的状态用的。其中的主要方法:

  • cancel,取消Callable的执行,当Callable还没有完成时
  • get,获得Callable的返回值
  • isCanceled,判断是否取消了
  • isDone,判断是否完成

二、用户线程与守护线程

百度面试的时候被问到怎么开一个守护线程,没有答上来。

在Java中有两类线程:

  • User Thread(用户线程):

    非守护线程包括常规的用户线程或诸如用于处理GUI事件的事件调度线程,Java虚拟机在它所有非守护线程已经离开后自动离开。

  • Daemon Thread(守护线程) :

    守护线程则是用来服务用户线程的,比如说GC线程。如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。

1、区别

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

2、创建

守护线程与普通线程写法上基本没什么区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。

1
2
3
4
5
Thread daemonTread = new Thread();     
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();
  1. thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
  2. 在Daemon线程中产生的新线程也是Daemon的。
  3. 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
Thread threadA=new Thread(){
public void run(){
System.out.println("hello I am threadA");
Thread threadB=new Thread(){
public void run(){
System.out.println("hello I am threadB");
}
};
System.out.println("threadB is deamon: "+threadB.isDaemon());
}
};
System.out.println("threadA is deamon: "+threadA.isDaemon());
threadA.setDaemon(true);
threadA.start();
System.out.println("threadA is deamon: "+threadA.isDaemon());
}

输出

1
2
3
4
threadA is deamon: false
threadA is deamon: true
hello I am threadA
threadB is deamon: true

可以看出,线程A初始状态下是非守护线程,设置后变成守护线程,而在线程A中产生的线程B,即使还没有start,但初始状态就已经是守护线程。

三、线程的5种状态

关于线程状态不同书上有不同的说法,一般来说有5种状态:

  1. 新建(new):

    ​ 当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

  2. 就绪(Runnable)

    ​ 一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(*thread scheduler*)来调度的。
    
  3. 运行(Running)

    ​ 当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

  4. 阻塞(Blocked)

    ​ 所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。线程运行过程中,可能由于各种原因进入阻塞状态:

    • 线程通过调用sleep方法进入睡眠状态;

    • 线程通过wait()方法挂起,JVM会把该线程放入等待池中,直到线程得到了notify()或者notifyAll()消息,线程才会进入就绪状态

    • 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;

    • 线程试图得到一个锁,而该锁正被其他线程持有;

  5. 死亡(Dead)

    ​ 该线程结束生命周期,可能是:

    • 从run方法返回
    • 线程被中断

三、参考地址

http://www.cnblogs.com/whgw/archive/2011/10/03/2198506.html

http://blog.csdn.net/longshengguoji/article/details/41126119