美文网首页
多线程创建

多线程创建

作者: ClawHub的技术分享 | 来源:发表于2019-05-11 22:25 被阅读0次

    程序、进程、线程的区别

    程序:是为完成特定任务、用某种语言编写的一组指令的集合。

    进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态
    的过程,有它自身的产生、存在和消亡的过程(即生命周期)。

    举例:运行中的QQ,360杀毒软件。

    线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。

    • 若一个进程同一时间并行执行多个线程,就是支持多线程的。
    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
    • 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

    举例:360运行时,同时进行病毒查杀,垃圾清理,优化加速等等功能时,每一个功能都是一个线程。

    单核CPU和多核CPU的理解

    1. 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时间单元特别短,因此感觉不出来。也就是说看起来是同时进行,其实是在不停切换。
    2. 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)。
    3. 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

    并行与并发

    并行:多个CPU同时执行多个任务。
    并发:一个CPU(采用时间片)同时执行多个任务。

    多线程程序的优点

    1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
    2. 提高计算机系统CPU的利用率。
    3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

    多线程的创建的4种方式

    1. 继承Thread类
    2. 实现Runnable接口
    3. 实现Callable接口
    4. 线程池

    ①继承Thread类

    步骤

    1. 创建一个继承于Thread类的子类
    2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
    3. 创建Thread类的子类的对象
    4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
    //继承Thread类
    public class Thread01 extends Thread {
        @Override
        //重写run方法
        public void run() {
        //this.getName()也可以
            System.out.println(Thread.currentThread().getName()+ "多线程启动啦");
        }
    }
    
    public class MyTest {
        public static void main(String[] args) {
        //创建Thread子类对象
            Thread01 td = new Thread01();
            //通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
            td.start();
            //td.run();
            System.out.println(Thread.currentThread().getName()+"我是主程序");
        }
    }
    

    结果

    main我是主程序
    Thread-0多线程启动啦
    

    注意

    1.我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。

    当我们把td.start()注释掉,换成td.run()。结果:

        main多线程启动啦
        main我是主程序
    

    全都变成主线程执行了,这是因为通过run方法启动线程其实就是调用一个类中的方法,当作普通的方法的方式调用。并没有创建一个线程。只有start方法才会在启动线程的同时,创建线程。

    2.如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()。
    我们在测试类中多次调用td.start()
    结果

    swl.practise.thread.MyTest
    Exception in thread "main" java.lang.IllegalThreadStateException
    Thread-0多线程启动啦
        at java.lang.Thread.start(Thread.java:708)
        at swl.practise.thread.MyTest.main(MyTest.java:7)
    

    原因

    image.png 问题就在这里,线程第一次启动时threadStatus为0,再次启动时不为0了,所以直接抛异常了。
    Thread类中方法
    void run()  将子线程要执行的操作写在方法体中。

    void start()  启动线程,并且调用线程的run()方法。

    ①线程名称

    String getName()  返回该线程的名称。

    void setName(String name)  设置线程名称,使之与参数 name 相同。

    如果不设置名字,线程也会有默认的名称。

    ②线程优先级

    int getPriority()   返回线程的优先级。

    void setPriority(int newPriority)   更改线程的优先级。

    线程优先级的范围由高到低为1-10,默认优先级为5,高优先级的线程并不是会一定被执行,只是抢占CPU执行权的几率变大。

    ③守护线程与用户线程

    boolean isDaemon()   测试该线程是否为守护线程。

    void setDaemon(boolean on)  将该线程标记为守护线程或用户线程。

    Java平台把操作系统的底层进行了屏蔽,在JVM虚拟平台里面构造出对自己有利的机制,这就是守护线程的由来。Daemon的作用是为其他线程的运行提供服务,比如说GC线程。如果用户线程结束,守护线程也会退出,因为他没什么好服务的了。setDaemon()方法必须在线程启动之前执行。

    public final native boolean isAlive() 判断当前线程是否存活。

    public static native Thread currentThread(); 获取当前执行的分线程。子线程中可以直接用this关键字,就代表当前线程。
    ④线程通信
    static void sleep(long millis) 线程睡眠指定毫秒,时间结束后,线程结束阻塞状态。

    void interrupt()  中断线程。这个之后再看,目前没时间。

    static void yield()  暂停当前正在执行的线程对象,并执行其他线程。

    void join()  等待该线程终止。

    从Object类继承来的方法  void notify() void wait()

    ②实现Runnable接口

    步骤

    1. 创建一个实现了Runnable接口的类。
    2. 实现类去实现Runnable中的抽象方法:run()。
    3. 创建实现类的对象。
    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
    5. 通过Thread类的对象调用start()。
    public class Thread02 implements Runnable {
        @Override
        public void run() {
            //这里不能this.getName() 因为接口里没有~~~~~
            System.out.println(Thread.currentThread().getName() + "启动了");
        }
    }
    
    public class MyTest {
        public static void main(String[] args) {
            Thread02 thread02 = new Thread02();
            //创建多个子线程,只需要用同一个子类对象就可以了
            //可以在创建时直接给线程起名字,继承Thread类也可以,不过需要在子类中提供参数为name的构造器
            Thread td1 = new Thread(thread02,"线程一号");
            Thread td2 = new Thread(thread02,"线程二号");
            td1.start();
            td2.start();
    
        }
    }
    

    结果

    swl.practise.thread.MyTest
    线程二号启动了
    线程一号启动了
    

    ③实现callable接口

    步骤

    1. 创建Callable子类的实例化对象
    2. 创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中(注意:FutureTask实现了Runnable接口和Future接口)
    3. 实例化Thread对象,并在构造方法中传入FurureTask对象
    4. 启动线程
    /*
     * 实现Callable接口创建子线程,指明范型为返回的数据类型
     * */
    public class CallDemo implements Callable<String> {
    
        @Override
        public String call() throws Exception {
            for (int i=0; i < 100 ; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
            return "执行完毕";
        }
    }
    
    public class TestCallable {
        public static void main(String[] args) {
            CallDemo cl = new CallDemo(); // 实例化Callable子类对象
            //要几个线程就要几个FutureTask对象哦
            FutureTask<String> ft1 = new FutureTask<String>(cl); // 实例化FutureTask对象,并将Callable子类对象传入FutureTask的构造方法中
            FutureTask<String> ft2 = new FutureTask<String>(cl); // 实例化FutureTask对象,并将Callable子类对象传入FutureTask的构造方法中
            Thread t1 =  new Thread(ft1, "1号线程输出——>"); // 启动线程
            Thread t2 = new Thread(ft2, "2号线程输出——>"); // 启动线程
            t1.start();
            t2.start();
        }
    }
    

    Callable接口相较于Runnable接口的好处:
    1.重写的call()相较于run()可以返回值
    2.返回值的类型可以通过泛型的方式指定
    3.重写的call()可以throws的方式处理异常

    ④线程池

    class MyThread implements Runnable {
    
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    
    }
    
    public class ThreadPool {
        public static void main(String[] args) {
            // 1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorService
            ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
            //设置管理参数
            pool.setMaximumPoolSize(20);
            // 2.将Runnable实现类的对象作为形参传递给ExecutorService的execute()方法中,开启线程
            // 并执行相关的run()
            pool.execute(new MyThread());
            pool.execute(new MyThread());
            pool.execute(new MyThread());
            
            // 3.结束线程的使用
            pool.shutdown();
    
        }
    }
    

    使用线程池的好处
    1.提高响应速度(减少了创建新线程的时间
    2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    3.便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没任务时最多保持多长时间后会终止

    面试

    继承Thread类和实现Runnable接口对比
    实现Runnable的方式相较于继承Thread类的方式更合适一些

    ① 实现的方式避开了类的单继承性的局限性
    ② 实现的方式更适合处理多个线程共享数据的情况(继承Thread类如果想实现资源共享,需要给共享的变量加static关键字)

    联系:public class Thread implements Runnable
    相同点:
    ① 创建的都是线程类的对象
    ② 启动线程,都是调用的Thread类中的start()
    说一下 runnable 和 callable 有什么区别?
    Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
    Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
    线程的 run()和 start()有什么区别?

    每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。

    start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

    run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

    相关文章

      网友评论

          本文标题:多线程创建

          本文链接:https://www.haomeiwen.com/subject/yryfaqtx.html