java多线程编程_java多线程编程

所谓的多线程编程本质上是并发编程,并发编程的本质是指发挥出所有硬件的最大性能。

6ccd96a2d1022bfbf462262e3abea89c.png    Java 是为数不多的真正支持有多线程并发编程的开发语言。所以Java 在整体的处理性能上是最高的。

如果要了解线程的性能问题,那么首先要先解决的就是清楚什么叫做进程?

f0fcdc160a70d098deb742681cce79d8.png

从计算机发展的历史来讲,传统的硬件只有一个 CPU(单核心的CPU),于是为了发挥出硬件的全部性能,引入了多进程的编程模式。

f105fba420ab8f97e6b0c0e01af96f4d.png

多进程是指在没有扩展原始系统硬件资源的情况下,利用一些算法,可以实现多个进程的并行执行。在同一个时间段上,会有多个进程并发执行,但是在同一个时间点上只会有一个进程执行。

线程实在进程基础上的进一步划分,可以达到更快的处理性能,任何一个进程的启动速度实际上都是非常缓慢的,所以线程本质上的设计性能上要远远高于进程,但是线程不可能离开进程存活。

62e7e32f8e81cb26f5ca1c0ff034bfe0.png

每一个进程的执行都必须有一个独立执行它的 CPU,所以所有的资源共享里只有 CPU是无法进行共享的,每个进程都有一个自己的中央处理单元。如果要想实现CPU的共享,那么就必须利用线程来描述。

随着硬件和软件的发展,硬件中的CPU出现了多核的状态,理论上多核CPU的多进程执行称为并行编程,并非所有的CPU都可以超出若干个线程出来,一般来讲,每一块CPU 只会有一个线程执行,但是有些CPU 可以使用超线程技术,设计出若干个多线程的执行状态。

在进程和线程的概念之上实际上还有一个所谓的”纤程“,在线程基础上的进一步划分,但有些地方称之为”协程“。Java并没有支持多协程编程(以后可能有)。现在比较常见的编程语言里:Kotlin(Android 第二代产品)和Python都是支持多协程编程的。

所有的Java 程序执行需要通过一个主方法完成,主方法会作为程序的起点。

若要进行多线程编程,也要有一个线程的起点结构。这个结构就称为线程类,所有的线程类都是有继承要求的,可以有三种实现方式:

继承Thread类;

实现Runnable接口;

实现Callable接口;

1)继承Thread实现多线程

java.lang.Thread 是由系统定义的一个线程处理类,任何的子类只需要继承此类,就可以得到一个线程处理的子类,在继承时一定要覆写Thread 类中的 run() 方法,那么这个方法成为线程启动的主方法存在。

范例:定义一个线程的主体类:

798f0d26e787a354132b50a260918601.png

如果要想正常的进行多线程的并发执行,那么就要调用本机操作系统提供的底层的函数支持,所以多线程的启动并不是依靠 run()完成的,他需要通过 Start()方法进行启动。而所有的Start() 启动后将调用 run()方法中定义的方法体。

public void start()

范例:启动多线程

class MyThread extendsThread{privateString name;publicMyThread(String name){this.name =name;

}

@Overridepublic voidrun() {//覆写run()方法

for (int i =0; i<50;i++){

System.out.println("【"+this.name+"- 线程】运行,i="+i);

}

}

}public classMyBlog2 {public static voidmain(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 voidstart() {if (threadStatus != 0)throw newIllegalThreadStateException();

group.add(this);boolean started = false;try{

start0();

started= true;

}finally{try{if (!started) {

group.threadStartFailed(this);

}

}catch(Throwable ignore) {

}

}

}privatenative voidstart0();

@Overridepublic voidrun() {if (target != null) {

target.run();

}

}

在每一个start()方法里都会抛出 “IllegalThreadStateException” 异常,而此异常是 RuntimeException 的一个子类,用户可以根据自己的需要选择性处理,此异常在重复启动多线程的时候才会抛出。

此时会发现 start()方法里定义了一个 start0()方法,而start0()方法没有方法体,但是使用了一个native的关键字定义,此关键字的作用在于此操作将交由底层实现。

381b024ef4b406f3b4fdcc22eb91ead1.png

在Java开发领域存在一个称为 JNI(Java Native Iterface)的技术,利用Java 技术调用底层操作系统函数,但是一般JavaEE 中这个开发较少见,因为这样写,Java的可移植性就会丧失,在之前的Android 开发里,JNI技术比较常见。

public classMyBlog2 {public static voidmain(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 接口。

@FunctionalInterfacepublic interfaceRunnable{public voidrun();

}

在Runnable 接口中同样存在有一个 run() 方法,此方法作为线程主方法存在。

使用Runnable实现多线程:

class MyThread implementsRunnable{privateString name;publicMyThread(String name){this.name =name;

}

@Overridepublic voidrun() {//覆写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 classMyBlog2 {public static voidmain(String[] args) {

MyThread threadA= new MyThread("线程A");

MyThread threadB= new MyThread("线程B");

MyThread threadC= new MyThread("线程C");newThread(threadA).start();newThread(threadB).start();newThread(threadC).start();

}

}

//程序执行结果(随机抽取):

【线程B- 线程】运行,i=0【线程C- 线程】运行,i=0【线程C- 线程】运行,i=1【线程C- 线程】运行,i=2【线程C- 线程】运行,i=3

//..........省略后面的结果了。。。。。。

那么此时就实现了与之前完全相同的操作功能,,但是很明显这样的实现避免了继承带来的单继承的局限,所以更适合项目的编写,同时在JDK1.8之后,Runnable成为了一个函数时接口,所以此时代码也可以使用 Lambda表达式进行定义。

范例:使用Lambda 实现多线程:

public classMyBlog2 {public static voidmain(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 接口的子类,所以可以得到如下结构图:

302e7e37870b863cb80570a2e7681529.png

解释问题:为什么Thread 接受了 Runnable 接口对象之后会去调用真实线程中的 run() 方法?

1、关注 Thread类中接受 Runnable 接口对象的构造方法:

publicThread(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()方法)

@Overridepublic voidrun(){if (target != null){

target.run();

}

}

可以发现在 Thread.run()方法定义的时候会判断是否有 target 实例,如果有实例,则会调用相应的run()方法;

在两种多线程实现方法下,实际上Runnable 接口相比较 Thread 而言,可以更加方便的描述数据共享的概念,即多个线程并行操作同一个资源(方法体)。

范例:观察资源共享:

class Mythread implementsRunnable{privateString name;private int ticket = 20; //共买20张票

@Overridepublic voidrun() {for (int x = 0; x < 50; x++) {if (this.ticket >0){

System.out.println("- 卖票:"+ticket--);

}

}

}

}public classMyBlog1 {public static voidmain(String[] args) {

Mythread threadBody=new Mythread();//定义多线程的公共处理

newThread(threadBody).start();newThread(threadBody).start();newThread(threadBody).start();

}

}

运行结果(部分抽取):- 卖票:20

- 卖票:19

- 卖票:18

- 卖票:16

- 卖票:17

- 卖票:14

classMyThread extendsThread{

privateString name;

publicMyThread(String name){

this.name=name;

}

@Overridepublic voidrun() {

//覆写run()方法for(inti =0; i<50;i++){

System.out.println("【"+this.name+"- 线程】运行,i="+i);

}

}

}

public classMyBlog2 {

public static voidmain(String[] args) {

MyThread threadA = newMyThread("线程A");

MyThread threadB = newMyThread("线程B");

MyThread threadC = newMyThread("线程C");

threadA.start(); //通过Thread 类继承而来threadB.start();

threadC.start();

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值