多线程之基础篇02:继承Thread类的方式创建多线程
标签: 多线程
一个例子
我们通过一个例子来学习如何通过继承Thread类的方式创建多线程。
题目:创建两个个子线程,每个子线程完成1-100之间的自然数的输出。同样地,主线程执行同样的操作。
要求:创建多线程的第一个方式:继承java.lang.Thread类
步骤:
首先我们要创建一个子线程类,这个类要继承继承java.lang.Thread类。
要重写Thread类中的run()方法,方法内实现此子线程要完成的功能。
在main()方法中创建一个子线程类的对象
通过子线程的对象,调用start()方法,start()方法有两个作用:
- 启动此线程
- 调用相应的run()方法
主线程执行
我们按照上述的步骤来写代码
package charThread;
//1. 创建一个继承于Thread类的子类
class SubThread extends Thread {
//2. 重写Thread类中的run()方法,方法内实现此子线程要完成的功能
public void run() {
for (int i = 0;i <= 100; i ++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
public class TestThread {
public static void main(String[] args) {
//3.创建一个子线程类的对象
SubThread st1 = new SubThread();
//4. 调用线程的start()方法,有两个作用:启动此线程,并调用相应的run()方法
st.start();
//主线程执行
for (int i = 0;i <= 100; i ++) {
System.out.println(Thread.currentThread().getName()+ "---" + i);
}
}
}
输出为:
Thread-0---0
Thread-0---1
main---0
Thread-0---2
Thread-1---0
Thread-0---3
main---1
Thread-0---4
Thread-1---1
Thread-0---5
main---2
main---3
main---4
main---5
Thread-1---2
Thread-1---3
Thread-1---4
Thread-1---5
Thread的常用方法
Thread的常用方法如下:
1. start()
:使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2. run()
:子线程中一定要重写该方法,将要执行的代码放入其中
3. currentThread()
:静态方法,返回对当前正在执行的线程对象的引用。
4. getName()
:获取此线程的名字
5. setName()
:设置此线程的名字
6. yield()
:调用此方法的线程释放当前CPU的执行权
7. join()
:在A线程中调用B线程的join()方法,表示:A线程停止执行,执行B线程,B线程执行完之后,再执行A线程
8. isAlive()
:判断当前线程是否还存活
9. interrupt()
:中断线程。
10. sleep(long l)
:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
11. setPriority(int priority)
:更改线程的优先级。
12. getPriority()
:获取线程的优先级
方法详解
1. start() 和 run()的区别说明
start()
: 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。run()
: run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程。
下面,通过一个简单示例演示它们之间的区别。源码如下:
// Demo.java 的源码
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public void run(){
System.out.println(Thread.currentThread().getName()+"正在运行);
}
};
public class Demo {
public static void main(String[] args) {
Thread mythread=new MyThread("子线程");
System.out.println(Thread.currentThread().getName()+" 呼叫子线程的run()方法");
mythread.run();
System.out.println(Thread.currentThread().getName()+" 呼叫子线程的start()");
mythread.start();
}
}
输出:
main 呼叫子线程的run()方法
main正在运行
main 呼叫子线程的start()
子线程正在运行
结果说明:
- Thread.currentThread().getName()是用于获取“当前线程”的名字。当前线程是指正在cpu中调度执行的线程。
- mythread.run()是在“主线程main”中调用的,该run()方法直接运行在“主线程main”上。
- mythread.start()会启动“线程mythread”,“线程mythread”启动之后,会调用run()方法;此时的run()方法是运行在“线程mythread”上。
2. currentThread()方法
static Thread currentThread()
:返回对当前正在执行的线程对象的引用。
public class Run1{
public static void main(String[] args){
System.out.println(Thread.currentThread().getName());
}
}
3. static void sleep() 方法
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
sleep方法有两个重载版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,进入停滞状态(阻塞当前线程),交出CPU资源,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
看下面这个例子就清楚了:
public class Test {
private int i = 10;
private Object object = new Object();
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread1 = test.new MyThread();
MyThread thread2 = test.new MyThread();
thread1.start();
thread2.start();
}
class MyThread extends Thread{
@Override
public void run() {
synchronized (object) {
i++;
System.out.println("i:"+i);
try {
System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
i++;
System.out.println("i:"+i);
}
}
}
}
输出结果:
i:11
线程Thread-0进入睡眠状态
线程Thread-0睡眠结束
i:12
i:13
线程Thread-1进入睡眠状态
线程Thread-1睡眠结束
i:14
从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。
注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
4. static void yield() 方法
用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
也就是说yield()执行以后,暂停该线程交出CPU,然后其他线程去抢占CPU(这个线程也有可能继续抢到)
我们看一下代码:
class MyThread extends Thread{
@Override
public void run() {
long beginTime=System.currentTimeMillis();
int count=0;
for (int i=0;i<50000000;i++){
count=count+(i+1);
//Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println("用时:"+(endTime-beginTime)+" 毫秒!");
}
}
public class Run {
public static void main(String[] args) {
MyThread t= new MyThread();
t.start();
}
}
执行结果:
用时:15 毫秒!
如果将 //Thread.yield();的注释去掉,执行结果如下:
用时:6080 毫秒!
5. join()方法
在A线程中调用B线程的join()方法,表示:A线程停止执行,执行B线程,B线程执行完之后,再执行A线程。即,调用join()方法的线程“加入”进来。
举个例子:
在很多情况下,主线程创建并启动了线程,如果子线程中要进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。
package charThread;
class JoinThread extends Thread {
public void run() {
for (int i = 0;i < 5;i++) {
System.out.println(getName() + ":" + i);
}
}
}
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
JoinThread jt = new JoinThread();
jt.setName("子线程");
jt.start();
for (int i = 0;i < 5;i++) {
if (i == 3) {
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
执行结果
main 0
main 1
main 2
子线程:0
子线程:1
子线程:2
子线程:3
子线程:4
main 3
main 4
由上可以看出main主线程等待joined thread线程先执行完了才结束的。如果把th.join()这行注释掉,运行结果如下:
如果将join()方法注释掉
main 0
main 1
main 2
main 3
main 4
子线程:0
子线程:1
子线程:2
子线程:3
子线程:4
6. isAlive()方法
方法isAlive()的功能是判断当前线程是否处于活动状态。
class MyThread extends Thread{
@Override
public void run() {
System.out.println("run="+this.isAlive());
}
}
public class RunTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread=new MyThread();
System.out.println("begin =="+myThread.isAlive());
myThread.start();
System.out.println("end =="+myThread.isAlive());
}
}
运行结果
begin ==false
end ==true
run=true
方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
有个需要注意的地方:
System.out.println("end =="+myThread.isAlive());
虽然上面的实例中打印的值是true,但此值是不确定的。打印true值是因为myThread线程还未执行完毕,所以输出true。如果代码改成下面这样,加了个sleep休眠:
public class RunTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread=new MyThread();
System.out.println("begin =="+myThread.isAlive());
myThread.start();
myThread.sleep(3000);
System.out.println("end =="+myThread.isAlive());
}
}
输出为:
begin ==false
run=true
end ==false
则上述代码运行的结果输出为false,因为mythread对象已经在3秒之内执行完毕。
7. interrupt()方法
Thread.interrupt()方法不会中断一个正在运行的线程。它的作用是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait
, Thread.join
和Thread.sleep
三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
interrupt方法并不是强制终止线程,它只能设置线程的interrupted状态,而在线程中一般使用一下方式:
while (!Thread.currentThread().isInterrupted() && more work to do) {
...
}
interrupt()方法并不会中断一个正在运行的线程。
我们可以看以下的代码:它创建了一个线程,并且试图使用Thread.interrupt方法停止该线程。Thread.sleep()方法的调用,为线程的初始化和中止提供了充裕的时间。线程本身并不参与任何有用的操作。
class Example1 extends Thread {
boolean stop=false;
public static void main( String args[] ) throws Exception {
Example1 thread = new Example1();
System.out.println( "Starting thread..." );
hread.start();
hread.sleep( 3000 );
System.out.println( "Interrupting thread..." );
thread.interrupt();
Thread.sleep( 3000 );
System.out.println("Stopping application..." );
//System.exit(0);
}
public void run() {
while(!stop){
System.out.println( "Thread is running..." );
long time = System.currentTimeMillis();
while((System.currentTimeMillis()-time < 1000)) {
}
}
System.out.println("Thread exiting under request..." );
}
}
输出结果:
Starting thread...
Thread is running...
Thread is running...
Thread is running...
Interrupting thread...
Thread is running...
Thread is running...
Thread is running...
Stopping application...
Thread is running...
Thread is running...
Thread is running...
在Thread.interrupt()被调用后,线程仍然继续运行。
那么如何真正地中断一个线程,我们以后再说。