栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java多线程8—Thread与Runnable

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java多线程8—Thread与Runnable

文章目录
  • 1. 创建多线程
    • 1.1 创建多线程的3种方式
      • 1.1.1 继承Thread类
      • 1.1.2 实现Runnable接口
      • 1.1.3 实现Callable接口
      • 1.1.4 基于线程池
    • 1.2 为什么要提出3种方式?
      • 1.2.1 Thread类的start()与run()
      • 1.2.2 3种创建方式的区别
    • 1.3 线程的声明周期
  • 2. Thread类源码解析
    • 2.1 Thread方法
    • 2.2 native方法
    • 2.3 其他方法
  • 3. 终止线程的4种方式
    • 3.1 正常退出
    • 3.2 使用标志位
    • 3.3 使用线程中断Interrupt
    • 3.4 stop方法(线程不安全)
  • 4. 常见问题
    • 4.1 虚假(过早)唤醒

1. 创建多线程

在Java中,实现线程的方式大体上分为三种,通过继承Thread类、实现Runnable接口,实现Callable接口。简单的示例代码分别如下所示。

1.1 创建多线程的3种方式

在了解此3种方式之前,我可以告诉你们任何方式都绕不开Thread类。

1.1.1 继承Thread类
// **测试类:**
public class MyThread extends Thread {
    
    public void run(){
        for(int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread() + ":" + i);
        }
    }
    
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        MyThread myThread3 = new MyThread();
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

// Thread类
public
class Thread implements Runnable {
	public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();	// 调用start0(),系统创建新线程
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                
            }
        }
    }
    
	// 注意,这里并不是没有实现方法,native关键字表示此方法在c/c++中实现,java源码中没有
    private native void start0();
}

疑问:

  1. 为什么继承Thread必须要重写run方法
  2. 为什么Thread类中start()并没有调用run(),而实际上却调用了呢?
1.1.2 实现Runnable接口
public class MyThread implements Runnable{
    public static int count = 20;
    
    @Override
    public void run() {
        while(count > 0){
            try {
                Thread.sleep(200);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-当前剩余票数:"+count--);
        }    
    }


    public static void main(String[] args) {
        MyThread Thread1=new MyThread();
        // 实际上还是将实现Runnable的实现类传入Thread初始化,与继承Thread类并重写run方法一样
        Thread mThread1=new Thread(Thread1,"线程1");
        Thread mThread2=new Thread(Thread1,"线程2");
        Thread mThread3=new Thread(Thread1,"线程3");
        mThread1.start();
        mThread2.start();
        mThread3.start();
    }
}
1.1.3 实现Callable接口

此方式基于FutureTask类和Callable接口,通过实现Callable接口重写run方法,然后生成FutureTask对象,由于FutureTask继承了Runnable接口,所以通过传入FutureTask对象来初始化Thread类,从而实现线程创建。

// 重写Callable接口
public class MyThread implements Callable{
    private int count = 20;
    @Override
    public String call() throws Exception {
        for (int i = count; i > 0; i--) {
//  Thread.yield();
            System.out.println(Thread.currentThread().getName()+"当前票数:" + i);
        }
        return "sale out";
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable callable  =new MyThread();
        FutureTask futureTask=new FutureTask<>(callable);
        Thread mThread=new Thread(futureTask);
        Thread mThread2=new Thread(futureTask);
        Thread mThread3=new Thread(futureTask);
//    mThread.setName("hhh");
        mThread.start();
        mThread2.start();
        mThread3.start();
        System.out.println(futureTask.get());
    }
}
1.1.4 基于线程池

此方法是将实现Runnable接口的类注入到线程池,线程池根据任务自动创建线程并执行,内部还是通过Thread类。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 创建线程池
public class MyThread{
    public static void main(String[] args) {

        ExecutorService ex= Executors.newFixedThreadPool(5);
        for(int i=0;i<5;i++) {
            ex.submit(new Runnable() {

                @Override
                public void run() {
                    for(int j=0;j<10;j++) {
                        System.out.println(Thread.currentThread().getName()+j);
                    }

                }
            });
        }
        ex.shutdown();
    }
}

// AbstractExecutorService接口
public Future submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

// ThreadPoolExecutor类
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))// 添加任务
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core) {

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);	// 根据任务创建执行者
        final Thread t = w.thread;	// 获得线程
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
				// ...
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();	// 执行线程的start方法
                workerStarted = true;
            }
        } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);	// 创建线程
}
1.2 为什么要提出3种方式?

通过上面的3种方法,我们了解了创建线程的3种方法,那么为什么要3种呢?一种不行吗?可以肯定这3中方式肯定在性能或适应场景上有差别。

还有Thread方法中并没有调用run方法,为什么还是执行了呢?

1.2.1 Thread类的start()与run()

关键在于那个native方法,这条语句表示:这个start0方法是在DLL库中定义的,由非java语言(如c/c++)书写的,调用由JVM底层完成,所以本方法在本地没有实现。

private native void start0();

其实Java平台有个用户和本地C代码进行交互操作的API,称为Java Native Interface (Java本地接口)。我们知道了这个方法是java调用底层c的接口。该过程由JVM底层来完成。

JNI如何与C语言进行通信呢?

public
class Thread implements Runnable {
    
    private static native void registerNatives();
    static {
        // 此方法将本地一些方法(DLL库)注册供当前类使用
        registerNatives();
    }
    //...
}

我们继续往下看,registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作。

JNIEXPORT void JNICALL 
 Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ 
   (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); 
 } 
 static JNINativeMethod methods[] = { 
    {"start0", "()V",(void *)&JVM_StartThread},
    {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, 
	 {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, 
	 {"suspend0","()V",(void *)&JVM_SuspendThread}, 
	 {"resume0","()V",(void *)&JVM_ResumeThread}, 
	 {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, 
	 {"yield", "()V",(void *)&JVM_Yield}, 
	 {"sleep","(J)V",(void *)&JVM_Sleep}, 
	 {"currentThread","()" THD,(void *)&JVM_CurrentThread}, 
	 {"countStackframes","()I",(void *)&JVM_CountStackframes}, 
	 {"interrupt0","()V",(void *)&JVM_Interrupt}, 
	 {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, 
	 {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, 
	 {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, 
	 {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, 
 };

我们找到了start(0),Java 线程调用 start 方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎样的逻辑呢?

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 
	…
	 native_thread = new JavaThread(&thread_entry, sz); 
	…

这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry

static void thread_entry(JavaThread* thread, TRAPS) { 
    HandleMark hm(THREAD); 
	 Handle obj(THREAD, thread->threadObj()); 
	 JavaValue result(T_VOID); 
	 JavaCalls::call_virtual(&result,obj, 
	 	KlassHandle(THREAD,SystemDictionary::Thread_klass()), 
	 	vmSymbolHandles::run_method_name(), 
 		vmSymbolHandles::void_method_signature(),THREAD); 
}

可以看到调用了 vmSymbolHandles::run_method_name 方法,这是在 vmSymbols.hpp 用宏定义的:

class vmSymbolHandles: AllStatic { 
	…
	 template(run_method_name,"run") 
	…
 }

至此,start调用run方法的过程一目了然,简单描述一下就是 Thread.start() —— JVM_StartThread—— JVM_ENTRY —— thread_entry —— Thread.run()

为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

JVM执行start方法,会另起一条线程执行thread的run方法,这才起到多线程的效果~ 「为什么我们不能直接调用run()方法?」 如果直接调用Thread的run()方法,其方法还是运行在主线程中,没有起到多线程效果。

1.2.2 3种创建方式的区别

继承Thread类与实现Runnable接口:

两者最本质的区别就是继承类与实现接口区别,Java中只能单继承,可以多实现,

因此实现Runnable接口的方式有以下优点:

  • 扩展性更好,避免了继承带来的局限性,
  • 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立
  • 适合多个相同代码的线程去处理同一个资源的情况。

继承Thread的优点

  • 封装了大部分常用代码,更好的重用了代码

因此,将Thread的run方法单独抽象成一种行为,也就是Runnable接口,以此提供更好的扩展性。

线程池方式与其他方式的区别:

不同于其他方式,我们直接将任务交给线程池,有线程池负责创建、管理、执行线程资源。这样的优点是:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。

Callable与其他方式的区别:

在前面几种方式中,都是使用Thread类与Runnable接口实现多线程,他们在处理并发问题中有一个缺陷就是执行结束后没有返回值,因此JDK1.5中提供了FutureTaksFutureCallable 类。

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

1.3 线程的声明周期

一个线程从创建,到最终的消亡,需要经历多种不同的状态,而这些不同的线程状态,由始至终也构成了线程生命周期的不同阶段。线程的生命周期可以总结为下图。

其中,几个重要的状态如下所示。

  • NEW:初始状态,线程被构建,但是还没有调用start()方法。

  • RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。

  • BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。

  • WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。

  • TIME_WAITING:超时等待状态。可以在一定的时间自行返回。

  • TERMINATED:终止状态,当前线程执行完毕。


2. Thread类源码解析

首先是几个构造函数,不用看。我们的重点关注其提供的几个静态方法和几个本地方法上

2.1 Thread方法

Thread中提供了很多静态方法,其中大部分是本地native方法,供多线程调度的,少部分用于线程对象属性设置

public
class Thread implements Runnable 
{
    public static native Thread currentThread();	// 获取当前线程
    
    // 设置优先级
    public final void setPriority(int newPriority) {}
    public final int getPriority() {
        return priority;
    }
    
    // 设置名称等
}
2.2 native方法

native是由其他语言书写的本地方法,由JVM调用。

{
    // 线程让步(运行态->就绪态),只是让出CPU资源,而不会释放锁资源
    // 线程让步是给优先级不低的线程竞争机会,可能让出后由立即重回执行态
    public static native void yield();
    
    // 线程睡眠(运行态->就绪态并保持一段时间),只是让出CPU资源,也不会释放锁资源
    public static native void sleep(long millis) throws InterruptedException;
    
    // 线程准备(初始态->就绪态),此时系统分配资源,但不会立即执行
    private native void start0();
    
    // 线程执行(就绪态->运行态),只有到这步,线程才真正开始执行任务
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    // 线程中断,注意,这里的中断是应用进程的中断而不是操作系统的中断,这里只是给线程一个中断信号,从而改变线程内部的中断标识位,线程并不会立即停止改变状态
    private native void interrupt0();
    
    // 线程休眠(**->初始态),会让出CPU资源并释放锁资源,只有其他线程的通知或中断才会唤醒,定义在Object类中
    // 注意:threadObj.wait()方法并不是让当前线程进入等待,而是让当前正在执行的线程进入threadObj的等待队列,即让出CPU时间片和锁资源。
    public final native void wait(long timeout) throws InterruptedException;
    
    // 抢占执行(**->初始态),在线程A执行时调用B.join(),会使A放弃CPU资源和锁资源从而执行线程B
    // 注意:wait(0)与wait()是无限等待直到线程执行完毕,而wait(10)则是等待10ms
	public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {	// isAlive方法是native方法,查看当前线程是否就绪
                wait(0);	//  将当前执行的线程放入此对象的等待队列上,而不是使当前线程进入等待
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    // 休眠唤醒(初始态->就绪态),唤醒在某对象监视器上等待的单个线程,此线程等待锁竞争和CPU时间片
    public final native void notify();
    
    // notify的加强版,唤醒在某对象监视器上等待的所有线程
    public final native void notifyAll();
    
    // 线程停止,使当前执行的run方法立刻返回并释放对象的所有锁
    // 线程不安全,可能造成数据不一致从而抛出异常
    private native void stop0(Object o);
}
2.3 其他方法
+ sleep:强迫某线程睡眠N毫秒
+ isAlive(): 判断一个线程是否存活。
+ activeCount(): 程序中活跃的线程数。
+ enumerate(): 枚举程序中的线程。
+ currentThread(): 得到当前线程。
+ isDaemon(): 判断一个线程是否为守护线程。
+ setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
+ setName(): 为线程设置一个名称。
+ wait(): 强迫一个线程等待
+ notify(): 通知一个线程继续运行。
+ setPriority(): 设置一个线程的优先级。
+ getPriority()::获得一个线程的优先级。

3. 终止线程的4种方式 3.1 正常退出 3.2 使用标志位

这种也是最常用的方法,就是定义一个boolean型的标志位,在线程的run方法中根据这个标志位是true还是false来判断是否退出,这种情况一般是将任务放在run方法中的一个while循环中执行的。

public class MyThread extends Thread {
    public volatile boolean exit = false;
    public void run(){
        int i = 0;
        while(!exit){
            System.out.println(i++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread1 = new MyThread();
        myThread1.start();
        sleep(100);
        myThread1.exit = true;
        myThread1.join();
        System.out.println("线程退出");
    }
}
3.3 使用线程中断Interrupt

线程处于阻塞状态:

当调用interrupt()方法时,会抛出InterruptException异常,通过捕获该异常,然后进行其他操作(结束线程)。

public class MyThread extends Thread {
    public void run()  {
        try {
            sleep(50000);  // 延迟50秒  
        }
        catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }
    public static void main(String[] args) throws Exception  {
        Thread thread = new MyThread();
        thread.start();
        System.out.println("在50秒之内按任意键中断线程!");
        System.in.read();
        thread.interrupt();
        thread.join();
        System.out.println("线程已经退出!");
    }
}

线程处于运行状态:

使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true。

public class MyThread extends Thread {
    public void run()  {
        int i = 0;
        while (!isInterrupted()) {
            //do something
            System.out.println(i++);
        }
    }
    public static void main(String[] args) throws Exception  {
        Thread thread = new MyThread();
        thread.start();
        thread.interrupt();
        thread.join();
        System.out.println("线程已经退出!");
    }
}
3.4 stop方法(线程不安全)

thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁,锁被破坏时,锁保护的数据有可能呈现不一致性。因此不推荐使用。

public class MyThread extends Thread {
    public volatile boolean exit = false;
    public void run(){
        int i = 0;
        while(!exit){
            System.out.println(i++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread1 = new MyThread();
        myThread1.start();
        sleep(100);
        //myThread1.exit = true;
        myThread1.stop();
        System.out.println("线程退出");
    }
}

4. 常见问题 4.1 虚假(过早)唤醒

我们常看见,wait的竞态条件为什么要放入死循环呢?普通的条件判断不行吗?

当然不行,如果不使用while,在线程被唤醒后可能出现错误操作,这里一个关键点是线程wait之后,被唤醒时会继续之前的上下文,如果之前在if内部进行的线程休眠,则唤醒之后直接出if执行下列操作,导致出现异常。

public class Test_1 {
    //元素列表
    private List list;
    //日期格式器
    private static final DateFormat format = new SimpleDateFormat("HH:mm:ss");
    //计数器
    private AtomicLong number = new AtomicLong();

    public Test_1() {
        list = new ArrayList<>();
    }

    //对list执行删除的元素
    public void remove() throws InterruptedException {
        synchronized (list){
            if (list.isEmpty()){
                //只要list为空,那么调用此方法的线程必须等待
                list.wait();
            }
            //如果执行到这里,说明list已经不为空了
            //这样执行元素的删除操作才不会出错
            String item = list.remove(0);
            System.out.println(Thread.currentThread().getName() + ": remove element " + item + "! "
                    + format.format(new Date()));
        }
    }

    //对list执行添加操作
    public void add(){
        synchronized (list){
            long currentTime = System.currentTimeMillis();
            //添加元素不要进行判断
            list.add(""+ number.incrementAndGet());
            System.out.println(Thread.currentThread().getName() + ": add item " + number.get()
                    + " " +format.format(new Date()));
            list.notifyAll();
            while (System.currentTimeMillis() - currentTime <= 1000){
                //assume do something
            }
        }
    }

    static class AddThread implements Runnable{
        private Test_1 es;

        public AddThread(Test_1 es) {
            this.es = es;
        }

        @Override
        public void run() {
            try {
                TimeUnit.MILLISECONDS.sleep(600);
                es.add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class RemoveThread implements Runnable{
        private Test_1 es;

        public RemoveThread(Test_1 es) {
            this.es = es;
        }

        @Override
        public void run() {
            try {
                TimeUnit.MILLISECONDS.sleep(100);
                es.remove();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        Test_1 es = new Test_1();
        for (int i = 0; i < 3; i++){
            new Thread(new RemoveThread(es),"RemoveThread" + i).start();
        }
        new Thread(new AddThread(es),"AddThread").start();
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/281652.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号