多线程学习笔记1
Abstract:
第一次接触多线程,现部分总结如下:
有三种方式实现多线程Java程序,本文只学习其中两种方式,并试图通过一个例子来解释多线程背后的本质和这两种调用方式之间的关系:
1.继承Thread类,重写run这个方法。
2.实现Runnable接口和其中的run方法。
现举例如下:
1 public class ThreadTest { 2 public static void main(String[] args) { 3 //调用方式见后面例子 4 } 5 } 6 7 //这是一个继承了Thread类的子类 8 class MyThread extends Thread{ 9 MyThread(Runnable myrunnable) { 10 super(myrunnable); 11 } 13 14 MyThread() {} 15 //被重写的run方法 17 public void run() { 18 System.out.println("in mythread run"); 19 System.out.println(Thread.currentThread.getName()); 20 } 22 23 } 24 25 //实现了Runnable接口的实例类 26 class MyRunnable implents Runnable{ 27 public void run() { 28 System.out,println("this is Runnable scope"); 29 System.out.println(Thread.currentThread.getName()); 30 31 } 32 }
一、第一种方式(继承Thread)
第一种方式就是直接继承Thread类——最简单的调用方法。
1 public static void main(String[] args) { 2 3 System.out.println(Thread.currentThread.getName()); 4 MyThread mythread = new Mythread(); 5 6 //这里的start()是调用start函数使得该线程进入就绪状态(不是执行的意思!) 7 Mythread.start(); 8 System.out.println("this is" + Thread.currentThread.getName()); 9 }
结果:
看到刚开始的时候当前线程是main函数,又看到在调用start函数之后当前线程变成了Thread-0。
分析:
从结果可以看到的是,继承Thread是一个类实现多线程机制的方式之一。通过调用该类的start方法(实际上是继承自Thread类)来启动多线程。并且从结果的最后两行可以看出多线程具体要做的任务——线程执行体就是重写Thread中的run方法。
二、第二种方式(实现Runnable接口)
如果去Java.lang文档中查看,会发现原来Thread实际上就是一个实现了Runnable接口的实例!所以很自然的想到应该还有一种实现多线程的方式——就是实现Runnable接口并重写run函数。这样的好处很多,比如说因为Java不支持多继承,所以一个子类想实现多线程,用继承Thread类的方式不可行,这时候便可以用实现Runnable接口的方式来实现。
但笔者根据了解,要启动多线程唯一的方式就是实例化一个Thread类并调用其start()方法,也就意味着首先实例化一个myrunnable类并实现了Runnable接口,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。所以本质上还是得经过Thread,但是这时候问题来了——如果使用接口实现,这个时候每个类中都实现了一个run,但是线程执行体到底是哪一个类中的?我们通过实例解释。
但这时候就有两种方法实例化Thread类——一种是直接使用Thread,一种是使用继承了Thread的子类。
1.1 Thread类
1 public static void main() { 2 System.out.println(Thread.currentThread.getName()); 3 Myrunnable myrunable = new Myrunnable(); 4 Thread thread = new Thread(myrunnable); //直接用Thread类 5 thread.start(); 6 System.out.println("this is " + Thread.currentThread.getName()); 7 8 }
结果:可以看到在Thread中的run函数没有被重写的时候(也就是直接调用Thread类的情况),线程执行体是Runnable接口中的run。
1.2 使用继承Thread的子类
public static void main() { System.out.println(Thread.currentThread.getName()); Myrunnable myrunable = new Myrunnable(); MyThread thread = new MyThread(myrunnable); //继承Thread的子类 thread.start(); System.out.println("this is " + Thread.currentThread.getName()); }
结果:发现当Thread中的run函数被重写之后,线程执行体就变成了Thread类中的run实例!
1.3 总结
这是Thread类中的run方法:
1 public void run() { 2 if (target != null) { 3 target.run(); 4 } 5 }
所以看到run方法的执行顺序实际上也很明确:因为Thread本身也实现了Runnable接口,意味着run方法最先是在Runnable接口中定义的。因此,在调用start()方法启动一个线程的时候,会首先检查Thread类中的run是不是被重写了,如果是,则调用该重写的run方法。没有的话,就去检查Runnable接口中run方法是否被重写,如果重写则调用。
总结一下就是:
run in Thread (继承子类)> run in Runnable(实现接口类)
三、线程状态
Java的线程一共有五种:
新建状态:Java新建一个进程对象 Thread mythread = new Thread();
就绪状态:调用线程对象的start()函数,等待CPU调度。
运行状态:线程run方法正式开始运行的时候,注意:运行状态的唯一入口是就绪状态。
阻塞状态:在线程执行的过程中,由于一些原因致使线程停止运行,直到其进入就绪状态之前的状态。
死亡状态:线程运行结束或者中间发生一些异常退出run方法(exitcode != 0)
有时候在程序的执行过程中会需要在不同的状态之间相互转换,下面就是几种方式:
3.1运行状态——>就绪状态:
使用yield() 方法可以将正在运行过程中的线程转换成为就绪状态(等待CPU调度的状态)。会让当前线程交出CPU权限执行其其他的权限,但是不能控制具体的交出CPU的时间,一旦交出之后只需要等待重新获取CPU执行时间即可,所以是就绪状态而不是阻塞状态。
1 public class Mythread extends thread { 2 3 @override 4 public void run() {
System.out.println("in thread run"); 5 long beginTime = System.currentTimeMillis(); 6 int count = 0; 7 for(int i=0; i<50000000; i++) { 8 count = count + (i + 1); 9 Thread.yield(); 10 } 11 long endTime = System.currentTimeMillis(); 12 System.out.println("用时" + (endTime - beginTime) + "毫秒"); 13 } 14 15 public class Run { 16 public static void main(String[] args) {
System.out.println(Thread.currentThread.getName()); 17 Mythread t = new Mythread(); 18 t.start();
System.out.println(Thread.currentThread.getName()); 19 } 20 21 } 22 }
结果:
3.2运行状态——>阻塞状态
使用方法sleep()可以在指定的时间毫秒数内让当前线程(this.currentThread())休眠。交出CPU去执行其他的任务。当睡眠时间满了之后,却不一定马上得到执行,因为此时CPU可能正在执行其他的任务。注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,
3.3运行状态——>死亡状态:
由于需要,是一些进程主动死亡或突出运行,暂停一个线程可以使用interrupt()。
持续更新中。。
四、相关参考链接
1. Java总结篇系列
2. Java多线程干货系列