所谓的多线程编程本质上是并发编程,并发编程的本质是指发挥出所有硬件的最大性能。
Java 是为数不多的真正支持有多线程并发编程的开发语言。所以Java 在整体的处理性能上是最高的。
如果要了解线程的性能问题,那么首先要先解决的就是清楚什么叫做进程?
从计算机发展的历史来讲,传统的硬件只有一个 CPU(单核心的CPU),于是为了发挥出硬件的全部性能,引入了多进程的编程模式。
多进程是指在没有扩展原始系统硬件资源的情况下,利用一些算法,可以实现多个进程的并行执行。在同一个时间段上,会有多个进程并发执行,但是在同一个时间点上只会有一个进程执行。
线程实在进程基础上的进一步划分,可以达到更快的处理性能,任何一个进程的启动速度实际上都是非常缓慢的,所以线程本质上的设计性能上要远远高于进程,但是线程不可能离开进程存活。
每一个进程的执行都必须有一个独立执行它的 CPU,所以所有的资源共享里只有 CPU是无法进行共享的,每个进程都有一个自己的中央处理单元。如果要想实现CPU的共享,那么就必须利用线程来描述。
随着硬件和软件的发展,硬件中的CPU出现了多核的状态,理论上多核CPU的多进程执行称为并行编程,并非所有的CPU都可以超出若干个线程出来,一般来讲,每一块CPU 只会有一个线程执行,但是有些CPU 可以使用超线程技术,设计出若干个多线程的执行状态。
在进程和线程的概念之上实际上还有一个所谓的”纤程“,在线程基础上的进一步划分,但有些地方称之为”协程“。Java并没有支持多协程编程(以后可能有)。现在比较常见的编程语言里:Kotlin(Android 第二代产品)和Python都是支持多协程编程的。
所有的Java 程序执行需要通过一个主方法完成,主方法会作为程序的起点。
若要进行多线程编程,也要有一个线程的起点结构。这个结构就称为线程类,所有的线程类都是有继承要求的,可以有三种实现方式:
- 继承Thread类;
- 实现Runnable接口;
- 实现Callable接口;
1)继承Thread实现多线程
java.lang.Thread 是由系统定义的一个线程处理类,任何的子类只需要继承此类,就可以得到一个线程处理的子类,在继承时一定要覆写Thread 类中的 run() 方法,那么这个方法成为线程启动的主方法存在。
范例:定义一个线程的主体类:
如果要想正常的进行多线程的并发执行,那么就要调用本机操作系统提供的底层的函数支持,所以多线程的启动并不是依靠 run()完成的,他需要通过 Start()方法进行启动。而所有的Start() 启动后将调用 run()方法中定义的方法体。
public void start()
范例:启动多线程
class MyThread extends Thread{ private String name; public MyThread(String name){ this.name =name; } @Override public void run() { //覆写run()方法 for (int i =0; i<50;i++){ System.out.println("【"+this.name+"- 线程】运行,i="+i); } } } public class MyBlog2 { public static void main(String[] args) { MyThread threadA = new MyThread("线程A"); MyThread threadB = new MyThread("线程B"); MyThread threadC = new MyThread("线程C"); threadA.start(); //通过Thread 类继承而来 threadB.start(); threadC.start(); } }
运行结果(随机抽取):
................... 【线程A- 线程】运行,i=49 【线程C- 线程】运行,i=0 【线程B- 线程】运行,i=0 【线程C- 线程】运行,i=1 【线程B- 线程】运行,i=1
..................
通过执行的结果,所有的线程对象属于交替的执行过程,并且都在交替执行run() 方法定义的方法体。进一步的解释:关于start() 方法?
首先观察start() 的实现源代码:
public synchronized void start() { 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) { } } } private native void start0(); @Override public void run() { if (target != null) { target.run(); } }
在每一个start()方法里都会抛出 “IllegalThreadStateException” 异常,而此异常是 RuntimeException 的一个子类,用户可以根据自己的需要选择性处理,此异常在重复启动多线程的时候才会抛出。
此时会发现 start()方法里定义了一个 start0()方法,而start0()方法没有方法体,但是使用了一个native的关键字定义,此关键字的作用在于此操作将交由底层实现。
在Java开发领域存在一个称为 JNI(Java Native Iterface)的技术,利用Java 技术调用底层操作系统函数,但是一般JavaEE 中这个开发较少见,因为这样写,Java的可移植性就会丧失,在之前的Android 开发里,JNI技术比较常见。
public class MyBlog2 { public static void main(String[] args) { MyThread threadA = new MyThread("线程A"); MyThread threadB = new MyThread("线程B"); MyThread threadC = new MyThread("线程C"); threadA.start(); //通过Thread 类继承而来 threadB.start(); threadC.start(); threadC.start(); //让他重复启动 } }
Exception in thread "main" java.lang.IllegalThreadStateException at java.base/java.lang.Thread.start(Thread.java:794) at ThreadTest.MyBlog2.main(MyBlog2.java:24) 【线程A- 线程】运行,i=0 【线程C- 线程】运行,i=0 ....................................
//省略下面。。
一个线程只允许启动一次。
2)实现 Runnable 接口
除了使用 Thread 类实现多线程之外,也可以使用java.lang .Runnable 接口来完成,首先观察一下Runnable 接口。
@FunctionalInterface public interface Runnable{ public void run(); }
在Runnable 接口中同样存在有一个 run() 方法,此方法作为线程主方法存在。
使用Runnable实现多线程:
class MyThread implements Runnable{ private String name; public MyThread(String name){ this.name =name; } @Override public void run() { //覆写run()方法 for (int i =0; i<50;i++){ System.out.println("【"+this.name+"- 线程】运行,i="+i); } } }
在之前继承了 Thread类实现的多线程,会自动将父类的 start方法继承而来,但若使用Runnable来实现,该接口并没有提供 start() 方法,同时关键型的问题是:多线程的启动只能够依靠Thread 类的 start() 方法。所以此时关注一下Thread类里提供的构造方法:
public Thread(Runnable target)
这个构造方法里面需要接受 Runnable 接口对象的实例。那么此时只需要按照标准调用即可。
范例:启动多线程:
public class MyBlog2 { public static void main(String[] args) { MyThread threadA = new MyThread("线程A"); MyThread threadB = new MyThread("线程B"); MyThread threadC = new MyThread("线程C"); new Thread(threadA).start(); new Thread(threadB).start(); new Thread(threadC).start(); } }
//程序执行结果(随机抽取):
【线程B- 线程】运行,i=0 【线程C- 线程】运行,i=0 【线程C- 线程】运行,i=1 【线程C- 线程】运行,i=2 【线程C- 线程】运行,i=3 //..........省略后面的结果了。。。。。。
那么此时就实现了与之前完全相同的操作功能,,但是很明显这样的实现避免了继承带来的单继承的局限,所以更适合项目的编写,同时在JDK1.8之后,Runnable成为了一个函数时接口,所以此时代码也可以使用 Lambda表达式进行定义。
范例:使用Lambda 实现多线程:
public class MyBlog2 { public static void main(String[] args) { String names[] = new String[]{"线程A","线程B","线程C"}; for (String name :names) { new Thread(()->{ for (int i =0; i<50;i++){ System.out.println("【"+name+"- 线程】运行,i="+i); } }).start(); } } }
运行结果(部分抽取): 【线程A- 线程】运行,i=3 【线程B- 线程】运行,i=26 【线程C- 线程】运行,i=30 【线程B- 线程】运行,i=27 【线程B- 线程】运行,i=28
从JDK1.8 之后 Lambda表达式的出现实际上可以达到简化线程类定义的功能。
- Thread 类与Runnable 接口的关系
在JDK 1.0 的时代就提供了 Thread类和 Runnable 接口,所以这两个实现方案就经常被人拿来比较,我将从两者实现的关联及区别进行说明,首先观察一下 Thread 类的定义结构:
public class Thread extends Object implements Runnable
可以发现此时的 Thread 类也是 Runnable 接口的子类,所以可以得到如下结构图:
解释问题:为什么Thread 接受了 Runnable 接口对象之后会去调用真实线程中的 run() 方法?
1、关注 Thread类中接受 Runnable 接口对象的构造方法:
public Thread(Runnable target) { this(null, target, "Thread-" + nextThreadNum(), 0); }
this.target = target;
private Runnable target;
当Runnable 接口传到 Thread 类中之后,会自动利用Thread类中的target 属性保存 Runnable 接口实例。
2、观察 Thread类中的 run() 方法(调用start()就调用Thread 类中的run()方法)
@Override public void run(){ if (target != null){ target.run(); } }
可以发现在 Thread.run()方法定义的时候会判断是否有 target 实例,如果有实例,则会调用相应的run()方法;
在两种多线程实现方法下,实际上Runnable 接口相比较 Thread 而言,可以更加方便的描述数据共享的概念,即多个线程并行操作同一个资源(方法体)。
范例:观察资源共享:
class Mythread implements Runnable{ private String name; private int ticket = 20; //共买20张票 @Override public void run() { for (int x = 0; x < 50; x++) { if (this.ticket >0 ){ System.out.println("- 卖票:"+ticket--); } } } } public class MyBlog1 { public static void main(String[] args) { Mythread threadBody =new Mythread();//定义多线程的公共处理 new Thread(threadBody).start(); new Thread(threadBody).start(); new Thread(threadBody).start(); } }
运行结果(部分抽取):
- 卖票:20
- 卖票:19
- 卖票:18
- 卖票:16
- 卖票:17
- 卖票:14
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name =name;
}
@Override
public void run() {
//覆写run()方法
for (int i =0; i<50;i++){
System.out.println("【"+this.name+"- 线程】运行,i="+i);
}
}
}
public class MyBlog2 {
public static void main(String[] args) {
MyThread threadA = new MyThread("线程A");
MyThread threadB = new MyThread("线程B");
MyThread threadC = new MyThread("线程C");
threadA.start(); //通过Thread 类继承而来
threadB.start();
threadC.start();
}
}