多线程
一、多线程
1、进程与线程
进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;
进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。
线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,
同时共享进程锁拥有的内存和其他资源。
2、Java标准库提供了进程和线程相关的API
进程主要包括表示进程的java.lang.Process类和创建进程的java.lang.ProcessBuilder类;
表示线程的是java.lang.Thread类,在虚拟机启动之后,通常只有Java类的main方法这个普通线程运行,运行时可以创建和启动新的线程;
有一类守护线程(damon thread),守护线程在后台运行,提供程序运行时所需的服务。当虚拟机中运行的所有线程都是守护线程时,虚拟机终止运行。
3、线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
-
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
-
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
-
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
-
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
- 新建状态:
4、线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
5、线程的创建
Java 提供了三种创建线程的方法:
-
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
1、通过实现 Runnable 接口来创建线程
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
示例:
1 package com.thread;
2
3 public class RunnableThreadTest implements Runnable
4 {
5
6 private int i;
7 public void run()
8 {
9 for(i = 0;i <100;i++)
10 {
11 System.out.println(Thread.currentThread().getName()+" "+i);
12 }
13 }
14 public static void main(String[] args)
15 {
16 for(int i = 0;i < 100;i++)
17 {
18 System.out.println(Thread.currentThread().getName()+" "+i);
19 if(i==20)
20 {
21 RunnableThreadTest rtt = new RunnableThreadTest();
22 new Thread(rtt,"新线程1").start();
23 new Thread(rtt,"新线程2").start();
24 }
25 }
26
27 }
28
29 }
2、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
1 package com.thread;
2
3 public class FirstThreadTest extends Thread{
4 int i = 0;
5 //重写run方法,run方法的方法体就是现场执行体
6 public void run()
7 {
8 for(;i<100;i++){
9 System.out.println(getName()+" "+i);
10
11 }
12 }
13 public static void main(String[] args)
14 {
15 for(int i = 0;i< 100;i++)
16 {
17 System.out.println(Thread.currentThread().getName()+" : "+i);
18 if(i==20)
19 {
20 new FirstThreadTest().start();
21 new FirstThreadTest().start();
22 }
23 }
24 }
25
26 }
上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。
3、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
示例:
1 package com.thread;
2
3 import java.util.concurrent.Callable;
4 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.FutureTask;
6
7 public class CallableThreadTest implements Callable<Integer>
8 {
9
10 public static void main(String[] args)
11 {
12 CallableThreadTest ctt = new CallableThreadTest();
13 FutureTask<Integer> ft = new FutureTask<>(ctt);
14 for(int i = 0;i < 100;i++)
15 {
16 System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
17 if(i==20)
18 {
19 new Thread(ft,"有返回值的线程").start();
20 }
21 }
22 try
23 {
24 System.out.println("子线程的返回值:"+ft.get());
25 } catch (InterruptedException e)
26 {
27 e.printStackTrace();
28 } catch (ExecutionException e)
29 {
30 e.printStackTrace();
31 }
32
33 }
34
35 @Override
36 public Integer call() throws Exception
37 {
38 int i = 0;
39 for(;i<100;i++)
40 {
41 System.out.println(Thread.currentThread().getName()+" "+i);
42 }
43 return i;
44 }
45
46 }
4、创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
5、小结:
在多线程的使用上:有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
另外在多线程编程时:
重点理解:
-
-
-
- 线程同步
- 线程间通信
- 线程死锁
- 线程控制:挂起、停止和恢复
-
-
参考博客:http://blog.csdn.net/escaflone/article/details/10418651
http://blog.csdn.net/longshengguoji/article/details/41126119
http://www.runoob.com/java/java-multithreading.html
http://www.tiantianbianma.com/java-wait-notify.html/