zoukankan      html  css  js  c++  java
  • 线程以及多线程开发

    摘自:https://www.cnblogs.com/gyyyblog/p/11823842.html

    线程以及多线程开发

     

    进程和线程

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

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

    1. 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()启动线程.

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

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

    邮箱:2754371607@qq.com 交友: 同款QQ号
     
    分类: Java
  • 相关阅读:
    Maidsafe-去中心化互联网白皮书
    The Top 20 Cybersecurity Startups To Watch In 2021 Based On Crunchbase
    Top 10 Blockchain Security and Smart Contract Audit Companies
    The 20 Best Cybersecurity Startups To Watch In 2020
    Blockchain In Cybersecurity: 11 Startups To Watch In 2019
    004-STM32+BC26丨260Y基本控制篇(阿里云物联网平台)-在阿里云物联网平台上一型一密动态注册设备(Android)
    涂鸦开发-单片机+涂鸦模组开发+OTA
    000-ESP32学习开发-ESP32烧录板使用说明
    03-STM32+Air724UG远程升级篇OTA(阿里云物联网平台)-STM32+Air724UG使用阿里云物联网平台OTA远程更新STM32程序
    03-STM32+Air724UG远程升级篇OTA(自建物联网平台)-STM32+Air724UG实现利用http/https远程更新STM32程序(TCP指令,单片机程序检查更新)
  • 原文地址:https://www.cnblogs.com/xichji/p/11824220.html
Copyright © 2011-2022 走看看