目录
多线程可以使程序在同一时间内执行多个操作,采用Java中的多线程机制可以使计算机资源得到更充分的利用,多线程技术在网络编程中有广泛的应用。
一、进程与线程
进程是程序的一次动态执行过程,它是从代码加载、执行中到执行完毕的一个完整过程,也就是进程本身从产生、发展到最终消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用中央处理器资源,或者共享操作系统的其他资源。由于CPU执行速度非常快,所有程序好像在“同时”运行一样。
在操作系统中可以多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程)。可以从Windows任务管理器中查看已启动的进程,进程是系统运行程序的最小单元。各进程之间是独立的,每个进程的内部数据和状态也是完全独立的。
线程是进程中执行运算的最小单位,是在进程基础上的进一步划分,一个线程可以完成一个独立的顺序控制流程。
与进程不同,同一进程内的多个线程共享同一块内存空间(包括代码空间、数据空间)和一块系统资源,所以系统在产生一个线程或在各线程之间切换工作,其负担要比在进程间切换小得多。
二、线程的优势
多线程作为一种多任务并发的工作方式,有着广泛的应用。合理使用线程,将减少开发和维护的成本,甚至可以改善复杂应用程序的性能。使用多线程的优势如下:
- 充分利用CPU的资源
- 简化编程模型
- 良好的用户体验
三、多线程编程
在Java语言中,实现多线程的方式有两种:一种是继承Thread类,另一种是实现Runnable接口。
1、Thread类介绍
Thread类提供了大量的方法来控制和操作线程。
方法 | 描述 | 类型 |
---|---|---|
Thread() | 创建Thread对象 | 构造方法 |
Thread(Runnable target) | 创建Thread对象,target为run()方法被调用的对象 | 构造方法 |
Thread(Runnable target,String name) | 创建Thread对象,target为run()方法被调用的对象,name为新线程的名称 | 构造方法 |
void run() | 执行任务操作的方法 | 实例方法 |
void start() | 使该线程开始运行,JVM将调用该线程的run()方法 | 实例方法 |
void sleep(long millis) | 在指定的毫秒数内让当前正在运行的线程休眠(暂停运行) | 静态方法 |
Thread currentThread() | 返回当前线程对象的引用 | 静态方法 |
Thread类的静态方法currentThread()返回当前线程对象的引用。在Java程序启动时,一个线程立即随之启动,这个线程通常被称为程序的主线程。
Thread类的重要性:
- 主线程是产生其他子线程的线程
- 主线程通常必须最后完成运行,因为它执行各种关闭动作
示例:
public class MainThreadTest {
public static void main(String[] args) {
Thread t=Thread.currentThread();
System.out.println("当前线程:"+t.getName());
t.setName("MainThread");
System.out.println("当前线程:"+t.getName());
}
}
以上示例中,使用Thread currentThread()方法获取当前线程,即主线程的Thread对象。在Thread中定义了name属性,记录线程名称,可以使用setName()方法或getName()方法对线程名称进行赋值和取值操作。
继承Thread类创建线程类
继承Thread类是实现线程的一种方式。在使用此方法自定义线程类时,必须在格式上满足如下要求:
- 此类必须继承Thread类
- 将线程执行的代码写在run()方法中
语法结构:
//继承Thread类的方式创建自定义线程类
public class MyThread extends Thread{
//重写Thread类中的run()方法
public void run(){
//线程执行任务的代码
}
}
示例:
public class WorkThread extends Thread{
public void run(){
System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");
}
}
class ThreadTest{
public static void main(String[] args) {
WorkThread workThread=new WorkThread();
workThread.setName("果农A");
workThread.start();
WorkThread workThread2=new WorkThread();
workThread2.setName("果农B");
workThread2.start();
}
}
运行结果:
果农B开始采摘苹果树:
果农A开始采摘苹果树:
果农B进度:第1颗
果农A进度:第1颗
果农B进度:第2颗
果农A进度:第2颗
果农A进度:第3颗
果农B进度:第3颗
果农B进度:第4颗
果农A进度:第4颗
果农B进度:第5颗
果农A进度:第5颗
果农B已完成采摘任务!
果农A已完成采摘任务!
从以上示例可以看出,两个线程对象调用start()方法启动后,每个线程都会独立完成各自的线程操作,相互之间没有影响并行运行。
实现Runnable接口创建线程类
Runnable接口位于java.lang包中,其中只提供一个抽象方法run()的声明,Thread类也实现了Runnable接口。使用Runnable接口时离不开Thread类,这是因为它要用Thread类中的start()方法。在Runnable接口中只有run()方法,其他操作都要借助于Thread类。
语法结构:
//实现Runnable接口方式创建线程类
class MyThread implements Runnable{
public void run(){
//这里写线程内容
}
}
//测试类
public class RunnableTest{
public static void main(String[] args){
//通过Thread类创建线程对象
MyThread myThread=new MyThread();
Thread thread=new Thread(myThread);
thread.start();
}
}
在上面的代码中,MyThread类实现了Runnable接口,在run()方法中编写线程所执行的代码。如果MyThread还需要继承其他类(如Base类),也完全可以实现。关键代码如下:
class MyThread extends Base implements Runnable{
public void run(){
//线程执行任务的代码
}
}
示例:
public class OneGroupThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "已完成任务!");
}
}
class TwoGroupThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");
for (int i = 0; i < 7; i++) {
System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "已完成任务!");
}
}
class Test {
public static void main(String[] args) {
OneGroupThread thread = new OneGroupThread();
TwoGroupThread thread2 = new TwoGroupThread();
thread.setName("一组阿哲");
thread.start();
thread2.setName("二组洋洋");
thread2.start();
}
}
}
}
运行结果:
一组阿哲开始为苹果树剪枝
二组洋洋开始为苹果树剪枝
一组阿哲进度:第1颗
二组洋洋进度:第1颗
二组洋洋进度:第2颗
一组阿哲进度:第2颗
二组洋洋进度:第3颗
二组洋洋进度:第4颗
一组阿哲进度:第3颗
二组洋洋进度:第5颗
一组阿哲进度:第4颗
二组洋洋进度:第6颗
二组洋洋进度:第7颗
一组阿哲进度:第5颗
二组洋洋已完成任务!
一组阿哲已完成任务!
2、线程调度相关方法
方法 | 描述 |
---|---|
int getPriority() | 返回线程的优先级 |
void setPrority(int newPriority) | 更改线程的优先级 |
boolean isAlive() | 测试线程是否处于活动状态 |
void join() | 进程中的其他线程必须等待该线程终止后才能运行 |
void interrupt() | 中断线程 |
void yield() | 暂停当前正在执行的线程类对象并运行其他线程 |
1、线程的优先级
范围是1~10;可以使用数字也可以使用以下三个静态常量
- MAX_PRIORITY:其值是10,表示优先级最高。
- MIN_PRIORITY:其值是1,表示优先级最低。
- NORM_PRIORITY:其值是5,表示普通优先级,也就是默认值。
优先级并不代表永远是该线程一直有运行的机会,而是,会获得更多的运行机会,优先级低,也是会获得运行机会的。
示例:
public class WorkThread2 implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗,优先级:"+Thread.currentThread().getPriority());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");
}
}
class RunnableTest{
public static void main(String[] args) {
WorkThread2 work1=new WorkThread2();
Thread t1=new Thread(work1,"果农A");
WorkThread2 work2=new WorkThread2();
Thread t2=new Thread(work2,"果农B");
WorkThread2 work3=new WorkThread2();
Thread t3=new Thread(work3,"果农C");
// Thread t1=new Thread(new WorkThread2());
// Thread t2=new Thread(new WorkThread2());
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.NORM_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
运行结果:
果农A开始采摘苹果树:
果农B开始采摘苹果树:
果农B进度:第1颗,优先级:1
果农C开始采摘苹果树:
果农A进度:第1颗,优先级:10
果农C进度:第1颗,优先级:5
果农A进度:第2颗,优先级:10
果农C进度:第2颗,优先级:5
果农B进度:第2颗,优先级:1
果农A进度:第3颗,优先级:10
果农C进度:第3颗,优先级:5
果农B进度:第3颗,优先级:1
果农A进度:第4颗,优先级:10
果农B进度:第4颗,优先级:1
果农C进度:第4颗,优先级:5
果农A进度:第5颗,优先级:10
果农B进度:第5颗,优先级:1
果农C进度:第5颗,优先级:5
果农C已完成采摘任务!
果农B已完成采摘任务!
果农A已完成采摘任务!
2、线程的强制运行
在线程操作中,可以使用join()方法让一个线程强制运行。在线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续运行。它有三个重载方法,定义如下:
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
注意:调用join()方法需要处理InterruptedException异常。
示例:
public class JoinThread implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");
}
}
}
//测试类
class Test3 {
public static void main(String[] args) {
//创建子线程并启动
Thread t=new Thread(new JoinThread(),"大型商超");
t.start();
Thread.currentThread().setName("果商");//修改主线程名称
//正常采购
for (int i = 0; i < 10; i++) {
if (i==5){
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");
}
}
}
运行结果:
果商采购进度:第1车
大型商超采购进度:第1车
果商采购进度:第2车
大型商超采购进度:第2车
果商采购进度:第3车
大型商超采购进度:第3车
大型商超采购进度:第4车
果商采购进度:第4车
果商采购进度:第5车
大型商超采购进度:第5车
大型商超采购进度:第6车
大型商超采购进度:第7车
大型商超采购进度:第8车
大型商超采购进度:第9车
大型商超采购进度:第10车
果商采购进度:第6车
果商采购进度:第7车
果商采购进度:第8车
果商采购进度:第9车
果商采购进度:第10车
在以上代码中,创建子线程类对象t代表大型商超,主线程代表果商。当向果商供应了五车水果后,改为向大型商超集中供应。在代码中,程序开始运行后,代码果商的主线程和代表大型商超的子线程交替运行。当条件满足后,执行t.join()方法,子线程会夺得CPU使用权,优先运行,子线程全部运行完毕后,代表果商的主线程恢复运行。
3、线程的礼让
当一个线程在运行中执行了Thread类的yield()静态方法后,如果此时还有相同或更高优先级的其他线程处于就绪状态,系统将会选择其他相同或更高优先级的线程运行,如果不存在这样的线程,则该线程继续运行。
注意:使用yield()方法实现线程礼让只是提供一种可能,不能保证一定会实现礼让,因为礼让的线程处于就绪状态时,还有可能被线程调度程序再次选中。
示例:
public class ChildThread implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName() + "品尝:第" + (i + 1) + "块");
}
}
}
class Test4 {
public static void main(String[] args) {
Thread t1=new Thread(new ChildThread(),"Child1");
Thread t2=new Thread(new ChildThread(),"Child2");
Thread t3=new Thread(new ChildThread(),"Child3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
Child3品尝:第1块
Child2品尝:第1块
Child1品尝:第1块
Child2品尝:第2块
Child3品尝:第2块
Child3品尝:第3块
Child3品尝:第4块
Child1品尝:第2块
Child3品尝:第5块
Child2品尝:第3块
Child1品尝:第3块
Child2品尝:第4块
Child1品尝:第4块
Child2品尝:第5块
Child1品尝:第5块
可以看出执行Thread.yield()方法之后,多线程交替运行较为频繁,提高了程序的并发性。
注意:
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU使用权,将运行机会让给其他线程,两者的区别如下:
1.sleep()方法会给其他线程运行机会,不考虑其他线程的优先级,因此较低优先级线程可能会获得运行机会。
2.yield()方法只会将运行机会让给相同优先级或更高优先级的线程。
3.调用sleep()方法需处理InterruptedException异常,而调用yeild()方法无此要求。