• 【Java 线程的深入研究1】Java 提供了三种创建线程的方法


    Java 提供了三种创建线程的方法:

    • 通过实现 Runnable 接口;
    • 通过继承 Thread 类本身;
    • 通过 Callable 和 Future 创建线程。

    1、通过实现 Runnable 接口来创建线程

    步骤:

    1,定义类实现Runnable接口

    2,覆盖Runnable接口中的run方法。

    将线程要运行的代码存放在该run方法中。

    3,通过Thread类建立线程对象。

    4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

    为什么要将Runnable接口的子类对象传递给Thread的构造函数。

    因为,自定义的run方法所属的对象是Runnable接口的子类对象。

    所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

    5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

    package com.multithread.runnable;
    class Thread2 implements Runnable{
        private String name;
    
        public Thread2(String name) {
            this.name=name;
        }
    
        @Override
        public void run() {
              for (int i = 0; i < 5; i++) {
                    System.out.println(name + "运行  :  " + i);
                    try {
                        Thread.sleep((int) Math.random() * 10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            
        }
        
    }
    public class Main {
    
        public static void main(String[] args) {
            new Thread(new Thread2("C")).start();
            new Thread(new Thread2("D")).start();
        }
    
    }

    输出:

    C运行  :  0
    D运行  :  0
    D运行  :  1
    C运行  :  1
    D运行  :  2
    C运行  :  2
    D运行  :  3
    C运行  :  3
    D运行  :  4
    C运行  :  4

    说明:
    Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
    在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
    实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

    2、通过继承Thread来创建线程。

    步骤:

    1,定义类继承Thread。

    2,复写Thread类中的run方法。

    目的:将自定义代码存储在run方法。让线程运行。//run();仅仅是对象调用方法。而线程创建了,并没有运行。

    3,调用线程的start方法,

    该方法两个作用:启动线程,调用run方法。

    Demo d = new Demo();//创建好一个线程。

    //d.start();//开启线程并执行该线程的run方法。

    d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。

    class Thread1 extends Thread{
        private String name;
        public Thread1(String name) {
           this.name=name;
        }
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + "运行  :  " + i);
                try {
                    sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
        }
    }
    public class Main {
    
        public static void main(String[] args) {
            Thread1 mTh1=new Thread1("A");
            Thread1 mTh2=new Thread1("B");
            mTh1.start();
            mTh2.start();
    
        }
    
    }

    输出:

    A运行  :  0
    B运行  :  0
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4
    B运行  :  1
    B运行  :  2
    B运行  :  3
    B运行  :  4

    再运行一下:

    A运行  :  0
    B运行  :  0
    B运行  :  1
    B运行  :  2
    B运行  :  3
    B运行  :  4
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4

    说明:
    程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
    注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
    从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
    Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
    实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

    但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

    Thread1 mTh1=new Thread1("A");
            Thread1 mTh2=mTh1;
            mTh1.start();
            mTh2.start();

    输出:

    Exception in thread "main" java.lang.IllegalThreadStateException
        at java.lang.Thread.start(Unknown Source)
        at com.multithread.learning.Main.main(Main.java:31)
    A运行  :  0
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4

     实现方式和继承方式有什么区别呢?

     

    实现方式好处:避免了单继承的局限性。

    在定义线程时,建立使用实现方式。

     

    两种方式区别:

    继承Thread: 线程代码存放Thread子类run方法中。

    实现Runnable,线程代码存在接口的子类的run方法。

     

    总结:

    实现Runnable接口比继承Thread类所具有的优势:

    1):适合多个相同的程序代码的线程去处理同一个资源

    2):可以避免java中的单继承的限制

    3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

    提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

     

    在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

    3、通过 Callable 和 Future 创建线程

    • 1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

    • 2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

    • 3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

    • 4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

    public class CallableThreadTest implements Callable<Integer> {
        public static void main(String[] args)  
        {  
            CallableThreadTest ctt = new CallableThreadTest();  
            FutureTask<Integer> ft = new FutureTask<>(ctt);  
            for(int i = 0;i < 100;i++)  
            {  
                System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
                if(i==20)  
                {  
                    new Thread(ft,"有返回值的线程").start();  
                }  
            }  
            try  
            {  
                System.out.println("子线程的返回值:"+ft.get());  
            } catch (InterruptedException e)  
            {  
                e.printStackTrace();  
            } catch (ExecutionException e)  
            {  
                e.printStackTrace();  
            }  
      
        }
        @Override  
        public Integer call() throws Exception  
        {  
            int i = 0;  
            for(;i<100;i++)  
            {  
                System.out.println(Thread.currentThread().getName()+" "+i);  
            }  
            return i;  
        }  
    }

    创建线程的三种方式的对比

    • 1. 采用实现 Runnable、Callable 接口的方式创见多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。

    • 2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

  • 相关阅读:
    gitlab安装笔记二_Centos7配置163yum源
    gitlab安装笔记一_虚拟机中安装Centos7
    初篇:我与Linux
    Redis分布式锁正确的实现方法
    MacOS从零开始搭建hexo博客
    docker服务编排--docker-compose
    多线程问题整理
    maven依赖范围
    linux打开文件数 too many open files 解决办法
    【数据结构】队列-数组模拟队列
  • 原文地址:https://www.cnblogs.com/guweiwei/p/6552941.html
Copyright © 2020-2023  润新知