Java之多线程

2018-09-06  本文已影响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,当主线程退出,子线程也自动销毁了。

上一篇下一篇

猜你喜欢

热点阅读