本章介绍Thread类中的核心方法。重点掌握如下关键技术点:
讲到线程,我们要先介绍进程。
我们可以将一个正在操作系统中运行的exe程序理解成一个“进程”。进程是受操作系统管理的基本运行单元。
线程可以理解成是在进程中独立运行的 子任务。比如,QQ.exe运行时就有很多的子任务在同时运行,比如:传文件、听音乐、发送表情等功能都有对象的线程在后台默默地运行。使用多线程有什么优点呢? 我们知道“多任务操作系统”。使用多任务操作系统,可以最大限度地利用CPU的空闲时间来处理其他的任务,比如一边让操作系统处理正在打印机打印的数据,一边使用Word编辑文档。而CPU在这些任务之间不停地切换,由于切换的速度非常快,给使用者的感受就是这些任务似乎在同时运行。所以,使用多线程技术后,可以在同一时间内运行更多不同种类的任务。
为了更加有效地理解多线程的优势,看一下如图1-3所示的单任务的模型图,理解一下单任务的缺点。
在图1-3中,任务1和任务2是两个完全独立、互不相关的任务,任务1是在等待远程服务器返回数据,一边进行后期的处理,这时CPU一直处于等待状态,一直在“空运行”。如果任务2是在10秒之后被运行,虽然执行任务2用的时间非常短,仅仅是1秒,但也必须在任务1运行结束之后才可以运行任务2。本程序是运行在单任务环境中,所以任务2有非常长的等待时间,系统运行效率答复降低。单任务的特点就是排队执行,也就是同步,就像在cmd中输入一条命令后,必须等待这条命令执行完才可以执行下一条命令一样。这就是单任务环境的缺点,即CPU利用率大幅降低。
而多任务的环境如图1-4所示。
多任务,cpu可以在任务1和任务2之间来回切换,使任务2不必等到10秒再运行,系统的运行效率大大得到提升。这就是要使用多线程技术、要学习多线程的原因。这是多线程技术的优点,使用多线程就是在使用异步。
在Java的JDk开发包中,已经自带了对多线程技术的支持。实现多线程编程的方式主要有两种,继承Thread类&实现Runnable接口。
我们看看Thread的源码:
Thread类实现了Runnable接口,它们之间具有多态关系。
其实,使用继承Thread类的方式创建新线程时,最大的局限就是不支持多继承。用Thread和Runnable两种方式创建的线程在工作时的性质是一样的,没有本质区别。
创建自定义线程类MyThread.java。重写run方法。在run方法中,写线程要执行的任务的代码:
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
测试类:
public class Client {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束!");
}
}
运行结果:
运行结果说明,在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。
线程是一个子任务,cpu以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
Thread.java类中的start()方法的作用是,通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才可以执行后面的代码。public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("运行中!");
}
}
如何使用这个MyRunnable呢?我们看一下Thread.java的构造函数:
public class Client {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!");
}
}
Thread.java类实现了Runnable接口,那也就意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他的线程进行调用。
演示数据共享的情况:
public class MyThread extends Thread {
private int count = 10;
@Override
public void run() {
super.run();
count--;
System.out.println("由 " + this.currentThread().getName() + ",计算,count=" + count);
}
}
public class Client {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread a = new Thread(myThread, "A");
Thread b = new Thread(myThread, "B");
Thread c = new Thread(myThread, "C");
Thread d = new Thread(myThread, "D");
Thread e = new Thread(myThread, "E");
Thread f = new Thread(myThread, "F");
Thread g = new Thread(myThread, "G");
Thread h = new Thread(myThread, "H");
Thread i = new Thread(myThread, "I");
Thread j = new Thread(myThread, "J");
a.start();
b.start();
c.start();
d.start();
e.start();
f.start();
g.start();
h.start();
i.start();
j.start();
}
}
某些JVM中,i--的操作要分成如下3步:
其实这个示例就是典型的销售场景:5个销售员,每个销售员卖出一个货品后不可以得出相同的剩余数量,必须在每一个销售员卖完一个货品后其他销售员才可以在新的剩余物品数上继续减1操作。这时就需要使多个线程之间进行同步,也就是用按顺序排队的方式进行减1操作。
更改代码如下:
public class MyThread extends Thread {
private int count = 10;
@Override
synchronized public void run() {
super.run();
count--;
System.out.println("由 " + this.currentThread().getName() + ",计算,count=" + count);
}
}
在run()方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁,如果上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法。这样也就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果了。Synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。
currentThread()方法可返回代码段正在被哪个线程调用的信息。
public class Client {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
}
继续试验,创建MyThread.java类:
public class MyThread extends Thread {
public MyThread() {
System.out.println("构造方法的打印:" + Thread.currentThread().getName());
}
@Override
synchronized public void run() {
System.out.println("run方法的打印:" + Thread.currentThread().getName());
}
}
运行client:
public class Client {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
public class Client {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// myThread.start();
myThread.run();
}
}
isAlive()的作用是测试线程是否处于活动状态。
public class MyThread extends Thread {
@Override
synchronized public void run() {
System.out.println("run=" + this.isAlive());
}
}
public class Client {
public static void main(String[] args) {
MyThread myThread = new MyThread();
System.out.println("begin==" + myThread.isAlive());
myThread.start();
System.out.println("end==" + myThread.isAlive());
}
}
活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程时“存活”的。
public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " begin");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args) {
Thread runThread = Thread.currentThread();
System.out.println(runThread.getName() + " " + runThread.getId());
}
}
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
System.out.println("i=" + (i + 1));
}
}
}
public class Client {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果,打印了500000行,说明没有停止线程。
public class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("mythread1 priority=" + this.getPriority());
MyThread2 thread2 = new MyThread2();
thread2.start();
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
System.out.println("mythread2 priority=" + this.getPriority());
}
}
public class Client {
public static void main(String[] args) {
MyThread1 thread1 = new MyThread1();
thread1.setPriority(6);
thread1.start();
}
}
public class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("i=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.setDaemon(true);
thread.start();
Thread.sleep(5000);
System.out.println("我离开thread对象也不再打印了,也就是停止了!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
本章介绍了Thread类的API