多线程编程1-定义理解与三种实现方式

多线程编程

进程与线程的理解:

进程: 是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程就是进程产生、发展到最终消亡二代过程;

多进程: 操作系统能同时运行多个进程(程序),由于CPU具备分时机制,在每个进程都能循环获得自己的CPU时间片;由于CPU执行的速度非常快,使得所有的程序好像是在同时运行一样。

image-20210402215645884

进程与线程的区别与联系:

  1. 进程是资源调度的基本单位,运行一个可执行程序会创建一个或多个线程,进程就是运行起来的可执行程序;
  2. 线程程序执行的基本单位,是轻量级的进程,每个进程中都有唯一的主线程,且只能有一个,主线程和进程是相互依存的关系,主线程结束,进程也会结束。

image-20210402220317818

具体实例(word):

每次启动Word对于操作系统而言就相当于启动了一个系统的进程,而在这个进程之上又有许多其他程序在运行(拼写检查等),那么对于这些程序就是一个个多线程。如果Word关闭了,则这些拼写检查的线程也肯定会消失,但是如果拼写检查的线程消失了,并不一定会让Word的进程消失;

多插一句:如果打开两个word文档,则表示当前操作系统创建了两个进程。

多线程实现:

实现多线程需要一个线程的主体类,这个类可以继承Thread、实现Runnable以及Callable接口完成定义;

Thread实现多线程:

继承结构如下:

public class Thread extends Object implements Runnable

实现接口Runnable,所以必须实现接口中的抽象方法:

Modifier and Type Method Description
void run() 当一个实现接口Runnable的对象被用来创建线程时,启动线程会导致对象的run方法在单独执行的线程中被调用。
void start() 使线程开始执行;Java虚拟机调用这个线程的run方法。

当产生多个对象时,这些对象就会并发的执行run()方法中的代码;

虽然多线程的执行方法都在run()方法中定义,但是在实际进行多线程启动时并不能直接调用此方法,由于多线程时需要并发执行的,所以需要通过操作系统的资源调度才能执行,这样多线程的启动就必须利用Thread类中的start()方法完成,调用此方法会间接的调用run()方法。

实例:

package Java从入门到项目实战.多线程编程.Java多线程实现;
class MyThread extends Thread{  //单继承
    private String title;
    public MyThread(String title){
        this.title = title;
    }
    //覆写线程的run方法
    @Override
    public void run() {
        for (int i = 0 ; i < 10; i++){
            System.out.println(this.title+"运行,i =" +i);
        }
    }
}
public class Main{
    public static void main(String[] args){
        new MyThread("线程A").start();   //实例化线程对象并启动
		new MyThread("线程B").start();
        new MyThread("线程C").start();
        
        //对照
        /*没有开启多线程*/
        new MyThread("线程A").run();
        new MyThread("线程B").run();
        new MyThread("线程C").run();
    }
}

由效果图可以看出,三个线程在交替执行:

image-20210403225445774

假如面试题:

为什么线程启动的时候必须调用start()方法而不是直接调用run()方法?

在本程序中,程序调用了Thread类继承而来的start()方法后,实际上他执行的还是覆写后的run()方法,那为什么不直接调用run()?

简单的说下:是因为多线程需要调用操作系统的资源,在start()下有一个关键的部分start0()方法,并且在start0()方法上使用了navite关键字定义;

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(); //navite

什么是navite?

navite是指:Java本机接口(Java Native Interface)简称:JNI;使用Java调用本机操作系统的函数功能完成一些特殊操作;

在Java中将start0()方法体交给JVM进行实现,所以这样就会出现在windows或者在Linux中实现的start0()的是不同,不关系过程,只关心结果(是否调用了本机的操作系统的函数);

start0()作用:交由JVM进行匹配不同的操作系统,实现start0()方法体,功能:实现本机函数的调用;

具体百度、Google吧。

Runnable接口实现多线程:

出现的原因:为了解决Thread实现多线程出现的单继承问题;并且增加了函数式接口;

Modifier and Type Method Description
void run() 当一个实现接口Runnable的对象被用来创建线程时,启动线程会导致对象的run方法在单独执行的线程中被调用。

实现代码:

class MyThread implements Runnable{
    private String title;
    public MyThread(String title){
        this.title = title;
    }

    @Override
    public void run() {  //线程方法覆写
        for (int i = 0; i< 10;i++){
            System.out.println(this.title+"运行,i"+i);
        }
    }
}

启动方式一:

Thread threadA = new Thread(new MyThread("线程A"));
Thread threadB = new Thread(new MyThread("线程B"));
Thread threadC = new Thread(new MyThread("线程C"));
Thread threadD = new Thread(new MyThread("线程D"));
Thread threadE = new Thread(new MyThread("线程E"));

threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();

启动方式二:

//通过Lambal表达式定义线程主体
for(int x = 0;  x < 3;x++){
    String title = "线程对象-"+x;
    //实际上Thread传入的类型是Runnable
    new Thread(()->{  //Lambda实现线程主体
        for(int y = 0; y < 20; y++){
            System.out.println(title+"运行,y"+y);
        }
    }).start();
}

Thread与Runnable的联系:

继承结构:

public class Thread extends Object implements Runnable

实际上在之前继承Thread类的时候实际上覆写的还是Runnable接口的run()方法。

实现并发访问资源:

package Java从入门到项目实战.多线程编程.Java多线程实现;
class MyThreadConcurrent implements Runnable {
    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //同步操作--》从5-1票数
            /*synchronized(this){
                if(this.ticket > 0){
                    System.out.println("卖票,ticket = "+this.ticket--);
                }
            }*/
            //票数乱数
            if(this.ticket > 0){
                System.out.println("卖票,ticket = "+this.ticket--);
            }

        }
    }
}
public class 并发资源访问 {
    public static void main(String[] args) {
        MyThreadConcurrent thread = new MyThreadConcurrent();
        new Thread(thread).start();  //第一个线程
        new Thread(thread).start();  //第二个线程
        new Thread(thread).start();  //第三个线程
    }
}

总结一句话:Thread有单继承的局限性以及在有些情况下结构的不合理性;所以后面多线程的实现使用的都是Runnable接口。

Callable接口实现多线程:

为什么要使用Callable接口来实现多线程?

因为使用Callable接口实现弥补了Runnable实现多线程没有返回值的问题。

继承结构如下:

@FunctionalInterface
public interface Callable<V>{
	public V call() throws Exception{
        
    }
}

定义的时候可以设置一个泛型,此泛型的类型就是call()方法的返回的数据类型,好处:可以避免向下转型的安全隐患。

线程类主体完成后,需要启动多线程的话还是需要通过Thread类实现的,又因为我们的Callable接口与Thread没有联系,所以我们需要FutureTask类实现两者之间的联系;如图所示:

image-20210404234544335

通过FutureTask类继承结构可以发现它是Runnable接口的子类;

代码实现如下:

package Java从入门到项目实战.多线程编程.Java多线程实现;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class CallableThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程执行 x = "+i);
        }
        return "xbhog";
    }
}

public class Callable接口实现多线程 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //将Callable实例化包装在FutureTask类中,这样就可以与Runnable接口关联
        FutureTask<String> task = new FutureTask<String>(new CallableThread());
        //线程启动
        new Thread(task).start();
        //获取call()的返回值
        System.out.println("【线程返回数据】:"+task.get());
    }
}
posted @ 2021-04-05 00:07  Xbhog  阅读(207)  评论(0编辑  收藏  举报