线程的实现
Thread类是专门用来创建线程和对线程进行操作的类。当某个类继承了Thread类之后,该类就叫做一个线程类。
在Java中通过run方法为线程指明要完成的任务,有两种方法来为线程提供run方法:
- **继承Thread类并重写它的run方法。**之后创建这个子类的对象并调用start()方法。
- **通过定义实现Runnable接口的类进而实现run方法。**这个类的对象在创建Thread的时候作为参数被传入,然后调用start()方法。
无论在哪种创建线程的两种方法中,均需调用线程对象的**start()方法以为线程分配必须的系统资源、调度线程运行并执行线程的run()**方法。
start()方法是启动线程的唯一的方法。start()方法首先为线程的执行准备好系统资源,然后再去调用run()方法。一个线程只能启动一次,再次启动就不合法了。
run()方法中放入了线程的工作,即我们要这个线程去做的所有事情。缺省状况下run()方法什么也不做。
在具体应用中,采用哪种方法来构造线程要视情况而定。通常,当一个线程已经继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。
下面给出两个例子来说明线程的两种实现方法,每个例子中都有两个线程:
线程的运行与停止
例子1 - 创建线程对象
代码
public class Main {
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
thread2.start();
}
}
class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println("Hello World: " + i);
}
}
}
class Thread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println("Welcome: " + i);
}
}
}
执行结果
Hello World: 0
Welcome: 0
Hello World: 1
Welcome: 1
Hello World: 2
Hello World: 3
Hello World: 4
Welcome: 2
Welcome: 3
Welcome: 4
由于这两个线程几乎在同一时间被置为runnable
状态,因此 Hello World 和 Welcome 大打印顺序完全又 JVM 对这两个线程的调度决定,我们不可提前预知。
例子2 - 创建线程对象
代码
public class Main {
public static void main(String[] args) {
// 线程的另一种实现方法,也可以使用匿名的内部类
Thread thread1 = new Thread(new MyThread1());
thread1.start();
Thread thread2 = new Thread(new MyThread2());
thread2.start();
}
}
class MyThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println("Hello: " + i);
}
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println("Welcome: " + i);
}
}
}
执行结果
Hello: 0
Hello: 1
Hello: 2
Hello: 3
Hello: 4
Welcome: 0
Welcome: 1
Welcome: 2
Welcome: 3
Welcome: 4
分析
Thread类也实现了Runnable接口,因此实现了接口中的run()方法。
当生成一个线程对象时,如果没有为其指定名字,那么线程对象的名字将使用如下形式:Thread-number,该number是自动增加的数字,并被所有的Thread对象所共享,因为它是一个static的成员变量。
当使用第一种方式(继承Thread的方式)来生成线程对象时,我们需要重写run()方法。
当使用第二种方式(实现Runnable接口的方式)来生成线程对象时,我们需要实现Runnable接口的run()方法,然后使用new Thread(new MyRunnableClass())来生成线程对象(MyRunnableClass已经实现了Runnable接口),这时的线程对象的run()方法会调用MyRunnableClass的run()方法。
停止线程
线程的消亡不能通过调用stop()命令,stop()方法是不安全的,已经废弃。
停止线程推荐的方式:设定一个标志变量,以让run()方法在执行完成时线程也结束执行。
具体的来说,在run()方法中是一个循环,由该标志变量控制循环是继续执行还是跳出;循环跳出,则线程结束。
代码演示
public class Main {
public static void main(String[] args) {
MyThreadClass r = new MyThreadClass();
Thread t = new Thread(r);
t.start();
// 当希望线程停止执行时,调用以下方法
r.stopRunning();
}
}
class MyThreadClass implements Runnable {
private boolean flag = true;
@Override
public void run() {
while (flag) {
System.out.println("Do something.");
}
}
public void stopRunning() {
flag = false;
}
}
多线程访问成员变量与局部变量
例子1 - 多线程访问成员变量
代码
public class Main {
public static void main(String[] args) {
HelloThread r = new HelloThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class HelloThread implements Runnable {
int i;
@Override
public void run() {
while (true) {
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name + " say: hello number: " + i++);
try {
Thread.sleep((long) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (10 == i) {
break;
}
}
}
}
运行结果
Thread-1 say: hello number: 0
Thread-0 say: hello number: 1
Thread-1 say: hello number: 2
Thread-0 say: hello number: 3
Thread-1 say: hello number: 4
Thread-0 say: hello number: 5
Thread-1 say: hello number: 6
Thread-0 say: hello number: 7
Thread-1 say: hello number: 8
Thread-0 say: hello number: 9
分析
由于HelloThread类的实例r对象被两个线程(t1与t2线程对象)共享,而i又为r对象的成员变量。因此,t1和t2线程会交替的数数(依次打印从0到9),直到i>10时结束。
例子2 - 多线程访问局部变量
当我们改变代码如下时(原先的成员变量i被注释掉,增加了方法中的局部变量i):
代码
public class Main {
public static void main(String[] args) {
HelloThread r = new HelloThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class HelloThread implements Runnable {
@Override
public void run() {
// 每一个线程都会拥有自己的一份局部变量的拷贝
// 线程之间互不影响
// 所以会打印20个数字,0到9每个数字都是两遍
int i = 0;
while (true) {
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name + " say: hello number: " + i++);
try {
Thread.sleep((long) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (10 == i) {
break;
}
}
}
}
运行结果
Thread-0 say: hello number: 0
Thread-1 say: hello number: 0
Thread-1 say: hello number: 1
Thread-0 say: hello number: 1
Thread-1 say: hello number: 2
Thread-0 say: hello number: 2
Thread-1 say: hello number: 3
Thread-0 say: hello number: 3
Thread-1 say: hello number: 4
Thread-0 say: hello number: 4
Thread-1 say: hello number: 5
Thread-0 say: hello number: 5
Thread-1 say: hello number: 6
Thread-0 say: hello number: 6
Thread-1 say: hello number: 7
Thread-0 say: hello number: 7
Thread-1 say: hello number: 8
Thread-0 say: hello number: 8
Thread-1 say: hello number: 9
Thread-0 say: hello number: 9
分析
如注释中所述,由于局部变量对于每一个线程来说都有自己的拷贝,所以各个线程之间不再共享同一个变量,输出结果为20个数字,实际上是两组,每组都是0到9的10个数字,并且两组数字之间随意地穿插在一起。
结论
- 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的,也就是说一个线程对成员变量的改变会影响到另一个线程。
- 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。
Reference
- Java 多线程(二) 线程的实现 - https://www.cnblogs.com/mengdd/archive/2013/02/16/2913637.html
- Java 多线程(四) 多线程访问成员变量与局部变量 - https://www.cnblogs.com/mengdd/archive/2013/02/16/2913659.html