美文网首页
Java之多线程

Java之多线程

作者: 如果仲有听日 | 来源:发表于2018-09-06 17:31 被阅读0次

    Thread(耦合,不推荐)

    Runnable(解耦,推荐)

    Executors

    ExecutorService

    Callable


    1. Thread概述

    Thread类在使用的方法在JDK手册中有介绍,它实现了Runnable接口中的唯一方法run(), run方法是线程执行的主体,因此在使用的时候需要重写run()方法去定义线程需要执行的功能,并且自定义Thread类的子类。

    线程的执行开始点是调用了Thread类中的start()方法,start方法中实际上是去调用重写的run方法,这样一个线程就开始执行了

    JDK手册中对两种创建和启动线程的例子

    Thread构造方法:

        Thread()

        Thread(Runnable target)

        Thread(Runnable target,String name)

        Thread(String name)

        Thread(ThreadGroup group,Runnable target)

        Thread(ThreadGroup group,Runnable target,String name)

        Thread(ThreadGroup group,Runnable target,String name, long stackSize)

        Thread(ThreadGroup group,String name)

    2. 继承Thread类创建运行线程

    main线程的退出不影响自定义线程的运行

    自定义线程类MyThread,使用默认线程名的构造方法:

    public class MyThread extends Thread{

        long minPrime;

        public MyThread() {}

        public void run() {

            int cnt = 0;

            while(cnt != 5) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                out.printf(" ThreadName[%s] myThread cnt:%d\n", super.getName(), cnt);

                cnt++;

            }

            out.println("myThread exit@@@@@@@@");

        }

    }


    2.1. 自定义线程先退出

    2.2. main线程先退出

    3. 关于线程的命名

    3.1. 获取线程名

    String getName();

    在线程中使用方法:super.getName(); 因为Mythread是继承了父类Thread,supper也可以不写,但是是调用的Thread中的方法

    在2中的线程没有对线程命名,默认是Thread-N(0,1,2,……,N),主线程的名字是Thread-main

    3.2. 获取当前线程的引用来获取线程名

    Thread类中的静态方法:static Thread currentThread();

    这个静态方法返回当前线程的Thread对象引用,在线程中拿到了Thread对象就能调用getName()方法了

    例:在main线程中不能直接按照3.1的方式来获取线程名,使用静态方法currentThread()来获取线程名。


    3.2. 自定义线程名

    我们也可以对每个线程自定义名字

    3.2.1. 通过构造方法设置线程名

    原理是Thread的构造方法除了空参构造方法,还可以加入线程名的构造方法

    Thread(String name)

    Thread(String name)构造方法

    3.2.2. 在启动线程之前设置线程名

    void setName(String name)

    4. 线程休眠sleep

    static void sleep(long millis)    毫秒级休眠 1000 = 1s

    static void sleep(long millis, int nanos)    纳秒级休眠 1/1000000000s = 1纳秒

    静态方法直接调用,休眠1s 休眠1微秒


    5. 实现Runnable接口创建启动线程(推荐使用)

    该方式是使用Thread的构造函数:Thread(Runnable target)

    参数是实现了Runnable接口抽象方法的一个子类

    public class RunnableThread implements Runnable{

        public RunnableThread() {}

        public void run() {

            int cnt = 0;

            Thread thd = Thread.currentThread();

            while(cnt != 5) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                out.printf(" ThreadName[%s] myThread cnt:%d\n", thd.getName(), cnt);

                cnt++;

            }

            out.println("myThread exit@@@@@@@@");

        }

    }

    6. 继承Thread方式和实现Runnable接口 方式对比

    7. 使用匿名内部类实现多线程

    复习一下内部类

    前提:

        要有继承或者接口实现

    语法:

        new 父类或者接口(){

            重写抽象方法abstractMethd(){

            }

        }


    7.1. 方式一:子类继承Thread,匿名子类

    其实就是子类继承Thread,子类匿名了

    7.2. 方式二:实现Runnable接口

    7.3. 方式三:前2种方法的结合,匿名第二种方式的接口实现类

    8. 线程的状态

    9. 线程池

    JDK5以前要使用线程池,开发人员需要用集合维护线程池:

    常用ArrayList:

    旧的线程池做法

    从JDK5开始,提供了内置线程池

    Executors类提供了创建线程池功能,是一个工具类都是静态方法

    线程池使用场景:

    假如你要做一个服务器端,一直在后台运行不会退出重启等操作,那么线程池是一个很好的选择,它避免了频繁创建销毁线程的系统开销,而且当线程池中的线程运行结束,线程池会回收该线程资源让其阻塞等待下一次被激活

    如果是非一直需要运行的后台服务器,则没有必要使用线程池,因为如果没有强行终止,线程池中的线程是会一直存在的,即便main线程退出也会存在

    9.1. 使用Executors工具类创建线程池

    Executors工具类中的方法创建线程池:static ExecutorService newFixedThreadPool(int nThreads)

        参数nThreads:是要固定创建的线程数量

        返回值ExecutorService:本身是一个接口,返回的是该接口实现类的对象

    9.1.1 Future submit(Runnable task)

    submit方法就是用来提交线程执行任务的,参数是Runnable接口的实现类

    定义一个类SubThread实现Runnable接口中的run方法:

    public class SubThread implements Runnable {

        private String thdName = null;

        private int intParam = 0;

        public SubThread(String thdName, int param){

            this.thdName = thdName;

            this.intParam = param;

        }

        public void run() {

            Thread thd = Thread.currentThread();

            int cnt = 0;

            thd.setName(thdName);

            while(cnt != 5) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                out.printf("  ThreadName[%s] thdN[%s] base[%d] myThread cnt:%d\n", thdName, thd.getName(), intParam, cnt);

                cnt++;

            }

            out.println("myThread exit@@@@@@@@");

        }

    }

    9.1.2. 使用Callable创建线程池

    Callable接口是一个跟Runnable功能类似的接口,但是功能比Runnable强大

    call方法

    Callable接口中也只有一个方法来描述线程任务,类似于Runnable中的run方法,但是call方法有一个泛型返回值,且可以抛出异常,这是run方法做不到的

    定义一个SubThreadCallable类:

    import java.util.concurrent.Callable;public class SubThreadCallable implements Callable {

        private String thdName = null;

        private int intParam = 0;

        public SubThreadCallable(String thdName, int param){

            this.thdName = thdName;

            this.intParam = param;

        }

        public Integer call() {

            Thread thd = Thread.currentThread();

            int cnt = 0;

            thd.setName(thdName);

            while(cnt != 5) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                out.printf("  ThreadName[%s] thdN[%s] base[%d] myThread cnt:%d\n", thdName, thd.getName(), intParam, cnt);

                cnt++;

            }

            out.println("subThread exit@@@@@@@@");

            return cnt;

        }

    }

    从运行结果可以看出2行submit代码瞬间运行打印出main1,之后在Integer i1 = ft.get(); 这一行阻塞,使用Callable的实现类作为submit方法的参数,会等待所有线程获取到了返回值才会向下继续执行,但是线程是异步执行的,结果确是同步返回。

    所以这种方式在main线程的最后一个返回值是阻塞的,跟python的线程很类似,改成异步锁效率更高,当释放一个线程就又使释放的线程又运行起来。

    再不要求返回值的情况下,应该尽量采用Runnable这种方式。


    改进版:

    让结果返回到最后去获取

    9.1.3.  void shutdown()

    启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。

    9.2. 非线程池 和 线程池 性能测试

    计算1+2+3+……+1000的总和

    思路:

        要做线程池和非线程池的对比,就分两个程序测试,并计算得出结果的时间差

        由于要想线程返回结果,目前只能用Callable方式来做(下一节可以用锁来做同步)

    9.2.1. 使用非线程池计算

    多次运算,diffTime是1600ms左右  

    9.2.2. 使用Callable线程池

    将1加到1000,分成2部分相加同时运行

    Callable 可见,运行速度提升了一倍

    10. Daemon Thread守护线程

    先看一段非守护线程的代码:

    子线程是非守护线程

    由于子线程是非守护线程,主线程退出了,子线程依然在运行。

    将子线程改为守护线程:

    子线程设置为守护线

    将子线程设置为了Daemon true,当主线程退出,子线程也自动销毁了。

    相关文章

      网友评论

          本文标题:Java之多线程

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