Java多线程——Thread与Runnable
一、进程与线程
进程——进程是系统资源分配、调度的基本单位,它可以申请和占用系统资源,每一个进程都有属于自己的地址空间,各进程相互独立。进程是程序的执行实体,程序本身是一个静态的概念,只有当程序执行时它才成为一个活动的实体,也就是进程。进程是线程的容器。
线程——线程是是程序执行的最小单元,它是进程的一个实体,是被系统调度和分派CPU的基本单位。线程自己不拥有系统资源(除自身运行的必须资源),同一个进程中的所有线程共享进程拥有的全部资源。
由于进程与线程的各自特点,所以进程通信比线程通信要困难的多。
二、Java多线程的实现方式
Java中对于多线程的实现方式主要有两种:扩展Thread类本身、实现Runnable接口。建议采用实现Runnable接口的方式。
2.1 扩展Thread类的实现方式
Thread常用方法:
方法 | 含义 |
---|---|
getName() | 获取线程名称 |
getPriority() | 获取线程优先级 |
isAlive() | 确认线程是否仍然在运行 |
join() | 等待线程终止 |
run() | 线程的入口 |
sleep() | 挂起线程一段时间 |
start() | 通过调用线程的run()方法启用线程 |
public class TicketerThreadExt extends Thread {
private Integer tickes;
public TicketerThreadExt(Integer tickes) {
this.tickes = tickes;
}
@Override
public void run() {
System.out.println("启动线程:开始售票");
System.out.println("线程名:" + Thread.currentThread().getName());
super.run();
while (tickes > 0) {
System.out.println("剩余票数: " + (--tickes));
}
System.out.println("车票售罄:终止线程");
}
public static void main(String[] args) {
TicketerThreadExt ticker = new TicketerThreadExt(5);
// 设置线程名
ticker.setName("Ticketer");
ticker.start();
}
}
执行结果:
启动线程:开始售票
线程名:Ticketer
剩余票数: 4
剩余票数: 3
剩余票数: 2
剩余票数: 1
剩余票数: 0
车票售罄:终止线程
2.2 扩展Runnable接口的实现方式
继承Runnable接口时,只需要实现run()方法就可以,在run()方法中可以调用其他方法、使用其他的类、也可以声明变量,它为并发线程的执行建立了入口。
// 售票员线程
public class TicketerThreadImpl implements Runnable {
private Integer tickets;
public TicketerThreadImpl(Integer tickets) {
this.tickets = tickets;
}
// 线程入口
public void run() {
System.out.println("启动线程:开始售票");
System.out.println("线程名:" + Thread.currentThread().getName());
// 售票操作
while (tickets > 0) {
System.out.println("剩余票数" + (--tickets));
}
System.out.println("车票售罄:退出线程");
}
public static void main(String[] args) {
TicketerThreadImpl ticker = new TicketerThreadImpl(5);
// 创建新线程,第二个参数为指定的线程名
Thread tickerWindow = new Thread(ticker, "Ticketer");
tickerWindow.start();
}
}
执行结果:
启动线程:开始售票
线程名:Ticketer
剩余票数4
剩余票数3
剩余票数2
剩余票数1
剩余票数0
车票售罄:退出线程
三、扩展Thread类与实现Runnable接口的比较(多线程情况)
- 实现Runnable方式可以避免扩展hread方式由于Java单继承特性的缺点
- 实现Runnable的代码可以被多个线程共享,适合多个线程处理同一资源的情况
- 扩展Thread可以通过某些方式增强或修改类的功能
实际开发中,创建子线程的最好方式是实现Runnable接口
下面对两种方式在多线程情况下的表现做下比较
3.1继承Thread的方式
public class TicketerExt extends Thread {
private Integer tickets;
public TicketerExt(String threadName) {
super(threadName);
this.tickets = 5;
}
@Override
public void run() {
System.out.println("启动线程:开始售票:线程名" + Thread.currentThread().getName());
super.run();
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售出1张,剩余票数:" + (--tickets));
}
System.out.println("车票售罄:终止线程" + Thread.currentThread().getName());
}
public static void main(String[] args) {
TicketerExt ticker1 = new TicketerExt("Ticker1");
TicketerExt ticker2 = new TicketerExt("Ticker2");
TicketerExt ticker3 = new TicketerExt("Ticker3");
ticker1.start();
ticker2.start();
ticker3.start();
}
}
执行结果:
启动线程:开始售票:线程名Ticker1
启动线程:开始售票:线程名Ticker3
启动线程:开始售票:线程名Ticker2
Ticker3售出1张,剩余票数:4
Ticker1售出1张,剩余票数:4
Ticker3售出1张,剩余票数:3
Ticker2售出1张,剩余票数:4
Ticker3售出1张,剩余票数:2
Ticker1售出1张,剩余票数:3
Ticker3售出1张,剩余票数:1
Ticker3售出1张,剩余票数:0
车票售罄:终止线程Ticker3
Ticker2售出1张,剩余票数:3
Ticker1售出1张,剩余票数:2
Ticker1售出1张,剩余票数:1
Ticker1售出1张,剩余票数:0
车票售罄:终止线程Ticker1
Ticker2售出1张,剩余票数:2
Ticker2售出1张,剩余票数:1
Ticker2售出1张,剩余票数:0
车票售罄:终止线程Ticker2
3.2、实现Runnable的方式
/**
* @author cyrus
* @version 1.0
* @desc description
* @date Create in 2018/01/30 15:47
*/
public class TicketerImpl implements Runnable {
private Integer tickets;
public TicketerImpl() {
this.tickets = 10;
}
// 注意在实际应用中,要对操作同一资源的执行块进行加锁,这里仅作对比用
public void run() {
System.out.println("启动线程:开始售票:线程名:" + Thread.currentThread().getName());
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售出1张票,剩余票数:" + (--tickets));
}
System.out.println("车票售罄:终止线程" + Thread.currentThread().getName());
}
public static void main(String[] args) {
TicketerImpl ticketer = new TicketerImpl();
Thread ticketer1 = new Thread(ticketer,"ticketer1");
Thread ticketer2 = new Thread(ticketer,"ticketer2");
Thread ticketer3 = new Thread(ticketer,"ticketer3");
ticketer1.start();
ticketer2.start();
ticketer3.start();
}
}
执行结果如下:
启动线程:开始售票:线程名:ticketer2
启动线程:开始售票:线程名:ticketer3
启动线程:开始售票:线程名:ticketer1
ticketer3售出1张票,剩余票数:8
ticketer2售出1张票,剩余票数:9
ticketer3售出1张票,剩余票数:6
ticketer1售出1张票,剩余票数:7
ticketer3售出1张票,剩余票数:4
ticketer2售出1张票,剩余票数:5
ticketer3售出1张票,剩余票数:2
ticketer1售出1张票,剩余票数:3
ticketer3售出1张票,剩余票数:0
ticketer2售出1张票,剩余票数:1
车票售罄:终止线程ticketer3
车票售罄:终止线程ticketer1
车票售罄:终止线程ticketer2
注意:
输出的结果不是按序输出,是因为线程在操作玩同一资源tickets后,即释放CPU资源,在之后的过程中才开始打印操作,在此过程中,CPU资源可能被其他线程抢占,因此才造成输出结果是乱序。这里仅仅是用作对比继承thread方式,所以没有进行加锁,实际操作中需要对抢占式资源进行互斥访问。