java多线程编程


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

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

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

 

 

 

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


 

 

 

 

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

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

 

 

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

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

     在进程和线程的概念之上实际上还有一个所谓的”纤程“,在线程基础上的进一步划分,但有些地方称之为”协程“。Java并没有支持多协程编程(以后可能有)。现在比较常见的编程语言里:KotlinAndroid 第二代产品)和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();
}
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM