导语
如果想要深入的了解一个东西就需要不断的接近他,这样才能了解到其核心的东西。这个系列就来了解一下Java多线程。开始入坑吧!
在入坑之前先来看一个例子!!
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
}
上面代码创建了一个被称为是主线程的东西,这主线程执行的效果就是打印出当前线程的名字,效果如下
控制台中输出的main其实就是这个线程的名字,需要声明的一点是控制台中输出的main与主函数main没有半点关系,只不过就是名称相同而已。
实现多线程的方式
在很多的面试中面试官都在问实现多线程有几种方式,其实对于一个资深程序员来说,实现多线程的方式很多,就看你自己怎么定义多线程算是多线程,Java官方提供的方式就是继承Runnable接口。我们知道在Runnable接口里面有一个run()方法。但是真正使得线程运行的方法是start方法,start方法底层实现是调用了一个private native void start0();来创建一个线程运行run()方法中的内容。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
继承Thread类
下面就来介绍一下Runnable的官方实现Thread,在Java中我们知道要实现父类的功能可以通过继承接口或者父类的方式进行。这里Thread就是继承了Runnable接口的实现类,通过继承这个实现类就可以使用它里面的一些方法。
1、继承Thread类实现自定义的线程类
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
2、编写测试类
public class Run {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束!");
}
}
运行上面代码会发现两个输入结果的顺序是不一定的。也就是说运行多线程的时候代码的先后顺序与代码最后执行的结果顺序是无关的。线程是作为一个子任务存在的,CPU不确定什么时候调用,或者说是CPU时间片轮转是随机。
这里我们来分析一个问题,既然start方法是作为启动子线程的方法,那么我们在同一个方法中重复的启动start方法是否可以创建多个线程呢?
public class Run {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.start();
System.out.println("运行结束!");
}
}
异常
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at com.thread.charp01.t1.mythread.Run.main(Run.java:13)
MyThread
进入到start方法中看到如下内容!
public synchronized void start() {
/**
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
其中有一个threadStatus表示线程的状态,如果threadStatus为 0 则表示一个新的线程,否则就会调用IllegalThreadStateException异常进行抛出。
3、测试CPU调用的随机性
public class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 10; i++) {
int time = (int) (Math.random()*1000);
Thread.sleep(time);
System.out.println("run= "+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
try{
MyThread thread = new MyThread();
thread.setName("MyThread");
thread.start();
for (int i = 0; i < 10; i++) {
int time = (int) (Math.random()*1000);
Thread.sleep(time);
System.out.println("main= "+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在代码中,随机挂起了某些线程。执行结果如图所示,从结果中可以看出CPU执行那个线程并不是确定的。
在返回对Start方法的分析,在Start方法中有个start0()的方法,它其实就是真正实现线程调用的方法,通过它将线程调用注入到一个类似于线程调度器中,然后等待CPU有时间的时候来进行调用然后执行run方法中的内容。这样就保证了整个的执行过程有中异步的感觉。但是如果线程直接调用的run方法则就是一个普通的方法调用。因为并没有一个新的线程被注入到线程调度器中,所以说还是在主线程中依次的去执行。
下面就来演示一个异步调用的例子
public class MyThread extends Thread {
private int i;
public MyThread(int i){
super();
this.i = i;
}
@Override
public void run() {
System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread(1);
MyThread t2 = new MyThread(2);
MyThread t3 = new MyThread(3);
MyThread t4 = new MyThread(4);
MyThread t5 = new MyThread(5);
MyThread t6 = new MyThread(6);
MyThread t7 = new MyThread(7);
MyThread t8 = new MyThread(8);
MyThread t9 = new MyThread(9);
MyThread t10 = new MyThread(10);
MyThread t11 = new MyThread(11);
MyThread t12 = new MyThread(12);
MyThread t13 = new MyThread(13);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
t9.start();
t10.start();
t11.start();
t12.start();
t13.start();
}
}
执行结果如下
从代码执行效果以及具体的逻辑来看,多线程的调用顺序与start方法调用的先后也是没有关系的。
上面就是通过继承了Runnable接口类的子类Thread类来实现的效果,当然在Java中对于类继承只能是单继承,但是对于接口的继承却是多继承的,如果在某些情况下,我们不但想实现多线程的功能,还想实现其他接口的功能,那么不妨试试下面这种方式
继承Runnable接口
1、创建自定义类
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("运行中");
}
}
2、创建测试类
public class Run {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束");
}
}
注意
在使用Thread的时候我们可以直接调用Thread类中的start方法,但是如果实现了Runnable接口,只是表示它由一个run()方法准备执行,但是具体交给谁执行还需要通过Thread将执行线程加入到线程调度器中,这样才能使得CPU在合适的时间对其进行调度。
总结
使用Thread类的方式开发多线程应用在程序设计上有一定的局限性,Java是单继承,不能支持多继承,所以使用Runnable可以打破这种局限性。