• 线程以及多线程开发


    进程和线程

    在学习线程之前,首先要理解什么是进程。打开你的任务管理器,导航栏第一个清清楚楚的写着进程,点进去会发现是许许多多的你在运行的程序,这就是一个进程。

    like this:

    现代操作系统都可以同时执行多个程序,这就是多任务。线程时建立在进程的基础上的,比如QQ音乐这个进程可以同时在执行播放、下载、传输等动作。这就叫多线程,每个线程在执行不同的功能。
    在单核CPU系统中,也可以同时运行多个程序,程序运行是抢占式的,QQ运行0.001S,chrome运行0.01s,这个时间人是感知不出来的,我们就会觉得在同时执行。所以为了提高效率,现在的手机、电脑都是非常多核的。

    进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。

    操作系统调度的最小任务单位其实不是进程,而是线程。

    进程 VS 线程

    进程和线程是包含关系,但是多任务既可以由多进程实现,也可以由线程实现,还可以混合多进程+多线程。

    和多线程相比,多进程的缺点是:

    • 创建进程比创建线程开销大很多,尤其是在Windows上
    • 进程间通信比线程要慢,因为线程见通信就是读写同一个变量,速度很快

    多进程的优点:

    • 多进程稳定性比多线程高,因为在多进程情况下,一个进程的崩溃不会影响其他进程,任何一个线程崩溃会导致整个进程崩溃。

    创建线程

    1. Thread

    例:

    public class MyThread extends Thread {  // 线程的主体类
        @Override
        public void run(){  
           System.out.println("Thread is starting");
        }
    }
    
    

    上面的MyThread 类继承Thread,覆写了run方法。一个类只要继承了此类,就表示这个类为线程的主体类。run()是线程的主方法,多线程要执行的方法都在这写。
    但是run()方法是不能被直接调用的,这牵涉到系统的资源调度问题,想要启动多线程,必须用start()完成。

    调用线程
    public class ThreadDemo {
        public static void main(String[] args) {
         new MyThread().start();
            // 启动新线程
    }
    

    java语言内置了多线程支持。当Java程序启动的时候其实是启动了一个JVM进程。JVM启动主线程来执行main()方法,在main()方法中可以启动其他线程。

    start() 只能由 Thread类型实例调用,表示启动一个线程。

    执行结果
    "C:Program FilesJavajdk1.8.0_221injava.exe"
    
    Thread is starting
    

    由此可见,线程创建成功

    那么创建一个多线程呢?

    创建多线程
    // 多线程主体类
    public class MyThread extends Thread {
        private String title;
        public MyThread(){
        }
        MyThread(String title){
            this.title = title;
        }
        @Override
        public void run(){
            for (int i = 0; i<10;i++){
                System.out.println(this.title +  "is starting");
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
    
    
    
     public static void main(String[] args) {
            new Thread(new MyThread("A"),"线程1").start();
            new Thread(new MyThread("C"),"线程2").start();
            new Thread(new MyThread("B")).start();
        }
    

    执行结果:

    这个结果中有几个关注点:

    1. 多线程的执行是无序的,不可控的
    2. 调用的是start()方法,但执行的是run()方法

    我们来看一下源码,分析一下

    public synchronized void start() {
          
            if (threadStatus != 0)  // 判断线程状态
            
            // 每一个线程的类的对象只允许启动一次,重复启动就会抛出这个异常,由run()抛出
                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();
       
       // 注释部分被我删掉了,太长了 
    

    我们发现start()方法调用的是start0(),而start0()并没有实现,还被native修饰,那么native是啥呢?

    在Java程序执行的过程中考虑到对于不同层次的开发者需求,支持了本地的操作系统函数调用。这项技术被称为JNI(Java Native Interface),但在Java开发过程中并不推荐这样使用。利用这项技术,可以利用操作系统提供的的底层函数,操作一些特殊的处理。

    不同的系统在进行资源调度的时候由自己的一套算法,要想调用start()方法启动线程,就要实现start0(),这时候JVM就会根据不同的操作系统来具体实现start0(),总结就是一切的一切都是跨平台带来的。

    这也规定了,启动多线程只有一种方案,调用Thread类中的start()方法.
    3. Thread 构造函数可以接收一个实例对象和线程的名字参数。

    Thread.currentThread().getName() 就代表了获取当前线程的名字。

    在返回值中还出现了"Thread-3",这是由于Thread会自动给没有命名的线程分配一个不会重复的名字。

    这种方式启动多线程固然没错,但存在单继承的隐患。下面就给出另一种模式。

    2. Runnable

    首先分别来看一下Thread类的实现

    public
    class Thread implements Runnable {}
    

    原来Thread是继承了Runnable

    再看一下Runnable接口

    @FunctionalInterface
    public interface Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    }
    

    再次惊讶,原来这个run方法也是从这里继承的。

    那就清楚了,来试一下吧。

    // 只需要实现 Runnable,重写run()即可,其他丝毫未变
    public class MyThread implements Runnable {
        private String title;
        public MyThread(){
        }
        MyThread(String title){
            this.title = title;
        }
        @Override
        public void run(){
            for (int i = 0; i<10;i++){
                System.out.println(this.title +  "is starting");
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
    
    
    
    
    public class ThreadDemo {
        public static void main(String[] args) {
            new Thread(new MyThread("A线程"),"线程1").start();
            new Thread(new MyThread("C线程"),"线程2").start();
            new Thread(new MyThread("B线程")).start();
           // lambda 语法实现
    //        new Thread(() -> {
    //           System.out.println("启动新的线程");
    //       }).start();
    
    
        }
    }
    

    结果:

    完全一致。

    在以后的多线程设计实现,优先使用Runnable接口实现。

    还没完,我们依靠Runnable接口实现的时候,会发现有一个缺陷,就是没有返回值,那有没有带返回值的实现方式呢?有!继续看

    3. Callable

    Java1.5之后为了解决run()方法没有返回值的问题,引入了新的线程实现java.util.concurrent.Callable接口.

    我们看一下Oracle的api文档:

    可以看到Callable定义的时候利用了一个泛型,代表了返回数据的类型,避免了向下转型带来的安全隐患

    了解向下转型可以看我的另一篇文章:https://www.cnblogs.com/gyyyblog/p/11806601.html

    但是问题又来了,我们上面已经说过了,要想启动多线程,必须使用Thread类提供的
    start() 方法调用Runnable接口的 run() 方法,可是现在 Callable中并没有run() 方法,那怎么办呢?

    再来找到一个FutureTask类:

    public class FutureTask<V>
    extends Object
    implements RunnableFuture<V>
    

    构造方法:

    它的构造方法可以接收一个Callable类型参数

    它又继承了RunnableFuture<V>,那就继续往上找

    public interface RunnableFuture<V>
    extends Runnable, Future<V>
    

    出现了,它出现了,Runnable我们知道了,是没有返回值的,现在看看Future<V>是个啥

    它有一个get()方法可以得到一个泛型返回值。

    OK,现在我们就可以梳理一下找到的这些东西怎么个关系:

    具体实现

    public class CallableThread implements Callable<String> {  // 继承实现Callable<V>
        // 覆写call()方法
        @Override
        public String call() throws Exception{
            for (int i = 0;i<10;i++){
                System.out.println("*********线程执行、i="+ i);
            }
            return "线程执行完毕";
        }
    }
    
    
    
    // 调用
            FutureTask<String> task = new FutureTask<>(new CallableThread());
            new Thread(task).start();
            System.out.println("【线程返回数据】" + task.get());
    

    结果:

    为了得到一个返回值可真不容易,核心思想还是实例化一个Thread对象,可通过其构造方法接收一个Rannable类型参数,调用start()启动线程.

    总结:基本来说就这三种创建多线程模式,根据场景使用。

    **纯属个人理解,希望指正错误,共同交流。

  • 相关阅读:
    【软件构造】第二章第一节 软件生命周期和版本控制(配置管理)
    【软件构造】第三章第三节 抽象数据型(ADT)
    【软件构造】第三章第二节 设计规约
    用python实现两个文本合并
    用python实现哈希表
    想要搭建项目 首选从概念理解(一)
    javascript调用rest地址,获取页面值
    ArcGIS Runtime SDK for Mac OS X使用示例
    ArcGIS Server网络分析模块问题汇总
    (ArcGIS Flex API)根据地图数据构建动态树
  • 原文地址:https://www.cnblogs.com/gyyyblog/p/11823842.html
Copyright © 2020-2023  润新知