1、线程概述
进程:运行在自己的地址空间之内的自包容的程序,是一个动态执行的过程。比如我打开记事本写一个.txt文件这个过程是一个进程;打开eclipse写一个程序这个过程是一个进程;运行这个程序也是一个进程;人跑50m短跑这个过程也可以说是一个进程。
线程:是比进程更小的执行单位,是一个个独立的子任务,一个线程就是在进程中的一个的单一的顺序控制流。比如运行一个.java文件时,主线程就是立刻启动运行;50m短跑的一个同学是一个线程;比赛100m x 4 这个过程是一个进程,这个进程给参赛的4位同学都分配了任务(每位同学跑100m)。
从上我们可以看到进程之间相互隔开,互不影响,彼此不干涉的;而同一个进程之间的线程之间时共享资源的(接力赛的接力棒),线程之间的执行顺序也是不确定的,在这个100 x 4 比赛中,每个同学跑第几棒是不确定的,顺序是由leader(这里我们假设是班主任)来确定的,此时班主任担任的就是一个CPU在分配资源的角色。
多线程:顾名思义就是一个进程中有多个线程。java的线程机制是抢占式,这表示调度机制会周期性地中断线程,讲上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都分配到数量合理的事件驱动它的任务。
注意:多线程是不能提高运行效率的,相反的在单处理器上还会降低一些运行效率,因为相对于所有部分顺序执行,多线程增加了上下文切换的代价(从一个任务到另一个任务),但是从程序设计、资源平衡、用户使用便捷等来看,多线程是很有必要的。
2、线程实现方法
- 实现Runnable接口,实现run()方法
class mythread implements Runnable{ public void run( ) {/* 实现该方法*/ } }
- 继承Thread类,重写run()方法
class mythread extends Thread { public void run( ) {/* 重写该方法*/ } }
3、线程的重要方法
常用的方法:
- start():继承Thread类时,调用Thread对象的start()方法为线程执行必要的初始化操作,启动线程,然后调用Runnable的run()方法运行线程。
- sleep(time):用于将线程挂起,线程休眠time毫秒。
- wait():中途释放锁,出来等候,但不重新排队。
- notify():与wait配套使用,通知wait()进程中心排队,排队顺序不确定,取决于JVM。
- notifyAll():通知所有wait()线程重新排队。
- yield():暂停目前正在执行的线程,切换其他的线程。
5、代码实现多线程
1. 线程可以驱动任务,实现多线程的方法之一就是声明一个实现 Runnable 接口的类,并实现 run() 方法
1 package a20190424; 2 import java.util.*; 3 4 public class Liftoff implements Runnable { 5 //@Override 6 7 protected int countDown = 10; 8 private static int taskCount = 0; 9 private final int id = taskCount++; //使用Final关键字,id的值不可更改 10 public Liftoff() {} 11 public Liftoff(int countDown) 12 { 13 this.countDown = countDown; 14 } 15 public String status() { 16 return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff ! " ) + "), "; 17 } 18 public void run() { 19 // TODO Auto-generated method stub 20 while(countDown -- > 0) 21 { 22 System.out.print(status()); 23 Thread.yield(); 24 } 25 26 } 27 28 } 29 30 31 32 package a20190424; 33 34 public class LiftMain { 35 public static void main(String[] args) { 36 Liftoff lift = new Liftoff(); 37 lift.run(); 38 } 39 40 }
输出结果
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff ! ),
2.Thread类,将Runnable对象转成工作任务的传统方式将其提交给Thread构造器
1 package a20190424; 2 import java.util.*; 3 4 public class Liftoff implements Runnable { 5 //@Override 6 7 protected int countDown = 10; 8 private static int taskCount = 0; 9 private final int id = taskCount++; //使用Final关键字,id的值不可更改 10 public Liftoff() {} 11 public Liftoff(int countDown) 12 { 13 this.countDown = countDown; 14 } 15 public String status() { 16 return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff ! " ) + "), "; 17 } 18 public void run() { 19 // TODO Auto-generated method stub 20 while(countDown -- > 0) 21 { 22 System.out.print(status()); 23 Thread.yield(); 24 } 25 26 } 27 28 } 29 30 31 32 package a20190424; 33 34 public class threadMain { 35 public static void main(String[] args) { 36 Thread t = new Thread(new Liftoff()); 37 t.start(); 38 System.out.println("Waiting for Liftoff"); 39 } 40 41 }
输出结果
Waiting for Liftoff #0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff ! ),
5、线程状态
一个线程可以处在一下四种状态之一
- 新建:线程对象已经建立,但是还没有启动,所以它还不能运行
- 就绪:想成等待分配处理器。也就是说在这种状态下,只要调度程序把时间分配给线程,线程就可以运行,也就是说在任意时刻,线程可以运行可以不运行,只要调度程序能分配时间片给线程,它就可以运行,这不同于死亡和阻塞状态。
- 死亡:线程死亡的方式往往是从run()方法返回。
- 阻塞:线程能够运行,但是由某个条件(调用了sleep() / wait() / 等待某个I/O完成 / 试图在某个对象上调用其同步控制方法,但是对象锁不可用)阻止它的运行,当线程处于阻塞状态时,调度机制将忽略线程,不会分配给线程任何处理时间,直到线程重新进入了就绪状态,它才有可能执行操作。