• 多线程编程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());
        }
    }
    
  • 相关阅读:
    Java之正則表達式【使用语法】
    2015年创业中遇到的技术问题:71-80
    2015年创业中遇到的技术问题:71-80
    Kinect小小玩偶游戏----小小潜水员
    微信开发学习日记(三):6点经验
    微信开发学习日记(二):3个案例
    2015年创业中遇到的技术问题:61-70
    2015年创业中遇到的技术问题:61-70
    2次创业经验谈(想创业想做事的人不要错过)
    Kinect舒适区范围--UE4 的Blueprint测试范例
  • 原文地址:https://www.cnblogs.com/xbhog/p/14617581.html
Copyright © 2020-2023  润新知