• 多线程二 基本技能


    前言

    线程驱动任务,而我们需要的就是一种任务的描述,而这个描述由Runable接口来提供,想定义任务,只需要实现Runable接口并重写里面的run()就好

    Thread的构造方法

    方法名 描述
    Thread() 创建新线程对象
    Thread(String name) 创建新线程对象
    Thread(Runnable target) 创建新线程对象
    Thread(Runnable target, String name) 创建新线程对象,name为指定的线程名
    Thread(ThreadGroup group, Runnable target) 分配新的 Thread 对象。
    Thread(Runnable target) 创建新线程对象

    Thread构造器通常需要一个Runable对象,我们把需要执行的任务放在run()中,程序运行后,会自动执行Runable对象的run()方法,以便在新的线程中执行我们指定的任务. start()方法是告诉cpu线程处于就绪状态

    一. 继承Thread 创建线程

    
    public class demo01 extends Thread {
    
        public  demo01(String name){
            super.setName(name);
        }
    
        public  demo01(){ }
    
        @Override
        public void run() {
            while(true)
            System.out.println(Thread.currentThread().getName()+"继承Thread类的线程执行了...");
        }
    
        public static void main(String[] args) {
    
            //new demo01()  执行Thread空参构造方法...
            demo01 d = new demo01();
            demo01 d2 =new demo01("helloThread");
    
            //线程就绪
            d.start();
            d2.start();
    
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    二. 直接实现Runnable接口

    实现runable接口 其实demo02 本类并不是一个Thread 他是一个线程任务

    
    public class demo02  implements  Runnable{
        @Override
        public void run() {
           // while(true)
            System.out.println(Thread.currentThread().getName()+"线程执行了...");
        }
    
        public static void main(String[] args) {
           demo02  d = new demo02();
            Thread myThread = new  Thread(d);
            myThread.start();
    
            //lamaban表达式实现的run方法
            new Thread(()->{
                while(true)
                System.out.println("lamaban表达式实现的run方法执行了...");
            }
            ).start();
    
            //直接写
            new Thread(){
                @Override
                public void run() {
                    System.out.println("hello Thread..");
                }
            }.start();
    
            //实现Runable() +  继承Thread();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Runnable接口里面的run");
                }
            }){
                //子类覆盖了 父类的run方法...
                @Override
               public void run(){
                    System.out.println("Thread子类里面的run");
                }
            }.start();
    
        }
    
    }
    
    

    一般情况下,如果不是多继承,实现Runable接口和继承Thread类没啥区别

    三. Callable 创建既有返回值,又可以抛出异常的线程

    Callable同样可以理解成是一个任务的描述方式,只不过他不能直接丢给Thread,而是交给一个叫FutrueTask的容器包装,

    Callable&Runable的区别

    • Callable规定的方法是call() 而后者是run()
    • Callable执行任务后可以拿到返回值,而后者不可以
    • call()方法,可以抛出异常,而run()方法不行
    • Callable更强大一些.运行Callable的任务,可以拿到一个Future的对象,我们可以通过这个对象isDone()判断任务是否结束,然后调用get()获取运行的返回值

    Future接口

    • 一来说多个线程之间是异步执行的,我们很难从一条线程拿到另一条线程的返回值,这个时候Futrue就出场了,对于Callable和Runable提交过来的任务,他可以进行查询任务是否完成 isDone() 获取执行结果get() 取消任务cancel()

    FutureTask类

    • FutrueTask它的父类是RunableFuture而RunableFuture继承了Runable和Future,看他的血缘关系,FutureTask当然既可以作为任务被线程执行,又可以拿到它得到的Callable的返回值,
    • 此外我们可以知道它最终也是执行Callable类型的接口,如果传递进来的是Runable的实现,那么它会先把他转化成Callable,
    public class demo03  implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("hello Callable");
            //返回值的类型就是Callable接口的泛型
            return 1;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            //未来的任务
            FutureTask<Integer> task = new FutureTask<>(new demo03());
    
            new Thread(task).start();
    
            System.out.println("返回值的结果是:"+task.get());
        }
    }
    
    

    刚才提到,Thread接收的任务是Runable类型的,现在FutureTask是个什么鬼?怎么把它传递进来的? 其实,FutrueTask实现了RunnableFuture接口,而这个接口继承了Runnable&Future,一切也就那么顺其自然了

    当然我们知道,Runable里面的run()方法是由新new出来的线程异步执行的,那么现在重写的这个call()怎么个运行法?

    查看FutrueTask的源码,我们可以看到,call()是由FetureTask的run()执行的,

     public void run() {
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             null, Thread.currentThread()))
                return;
            try {
                Callable<V> c = callable;
                if (c != null && state == NEW) {
                    V result;
                    boolean ran;
                    try {
                        result = c.call();
                        ran = true;
    

    run()方法&call()方法

    • Runable的Run方法 是由 线程异步调用的

    虽然是耗时的操作,如果可能出现阻塞,由新的线程中执行,会节省时间

    • Callable的 call 方法,同步调用的,是由Future的run方法调用的,而这个run方法,是对Runable接口里面run()的重写

    依然是耗时的操作

    在往线程池中提交任务时 submit()方法同样可接受Callable对象,后续会详解

        Future<String> future = threadPoolExecutor.submit(new Callable<String>() {
                    @Override
                    public String call(){
                        ai.getAndIncrement();
                        return Thread.currentThread().getName();
                    }
                });
    

    四. Thread常用API

    1 . interrupt() & isInterrupted()

    interrupted():

    • 作用: 中断当前线程,但是! 相比于废弃的stop() 并不是真正意义上的中断,而是打上了一个标记, 表示想要中断它.但是呢?在中断它之前要让他把该做的事,该跑的代码 跑完!
    • 特性:
      • 第一次执行interrupt()----> 标记当前线程是要被中断的
      • 第二次执行interrupt()----> 清除所有标记

    它要配合 isInterrupted() ,作为条件,判断当前的状态,去中断, 本函数多次调用不会 改变 当前线程的状态

    实例代码

    停不下来的线程

    /*
    * 停止不了的线程
    * */
    public class CanNotStop extends Thread{
    
    AtomicInteger value = new AtomicInteger(1);
    
    @Override
    public void run() {
        value.getAndIncrement();
        System.out.println("当前的value== "+value);
        /*
        * isInterrupted()  方法, 判断当前线程的状态去中断线程,
        * 假如说,当前线程被标记为要去中断, 被isInterrupted()限制的Target方法不再执行,条件之外的代码,能且只能执行一次! 然后彻底被中断
        * */
        while(!isInterrupted()) {
    
            try {
                sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行了..");
        }
    
        System.out.println("你真的以为我被中断了吗?");
    }
    
    
        public static void main(String[] args) {
    
        //初始化
        CanNotStop canNotStop = new CanNotStop();
        CanNotStop canNotStop2 = new CanNotStop();
    
    
    
        canNotStop.start();
        canNotStop2.start();
    
        /*
        * 关于interrupt()方法,这个方法
        *         作用:
        *           中断当前线程,但是! 相比于废弃的stop() 并不是真正意义上的中断,而是打上了一个标记, 表示想要中断它.但是呢?在中断它之前要让他把该做的事,
        *     该跑的代码 跑完!
        *         特性:
        *            第一次执行interrupt()----> 标记当前线程是要被中断的
        *           第二次执行interrupt()---->  清除所有标记
        *
        *     它要配合 isInterrupted() ,作为条件,判断当前的状态,去中断, 本函数多次调用不会 改变 当前线程的状态
        * */
    
        canNotStop.interrupt();
    
        try {
            sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        //被中断就相当于死掉了?
        synchronized (canNotStop){
            canNotStop.notifyAll();
        }
    
        }
    }
    
    

    这也称作是停止不了的线程,为啥这样说呢? 可以看到,执行 canNotStop.interrupt();代码的是谁? 没错主线程!细想一下,执行这个语句时,新创建的线程,本来就没有抢到CPU的执行权,也就是说他本来就是停止的... 他的原理就是打个标记,等到被打上标记的线程抢到CPU的执行权的时候,去判断一下就行了,如果存在被中断的标记,就什么事都不做,反之则执行任务.其实,它还活着

    2 currentThread()

    返回当前代码,正在被哪个线程调用

    3 isAlive()

    判断当前线程是否处于存活状态,线程处于正在运行或者等待运行的状态返回true,线程运行完毕返回false

    4 sleep()

    指定在线程休眠的时间,在指定的时间后,重新进入就绪状态去竞争CPU的执行权

    5 getId() & getName()

    返回线程的id & 名字

    6 停止线程

    • 异常停止
    throw new InterruptedException();
    
    • 在睡眠中停止

      • 先被打上停止的标记interrupt(),再遇到sleep(),程序直接进入catch块
      • 先睡眠sleep(),再被打上Interrupt(),程序进入catch块,并且清除停止状态值,使之变成false;
    • 暴力停止

      • stop(),线程立即停止,不再执行后续的代码,已被废弃
    • return停止线程

    @Override
    public void run(){
        while(true){
            if(this.isInterrupted()){
               return; 
            }
            System.out.println("if 后面的代码");
        }
    }
    
    

    推荐使用的是剖出异常的停止线程的方法,因为有了异常之后,可以在catch块中对异常进行处理,让程序更加流畅

    7 yield()

    • 让当前的线程放弃对cpu的使用权,但是也可能会发生,刚放弃就重新获取到了执行权的情况
    • 不会释放锁

    8 暂停线程

    • suspend() 暂停线程被废弃
    • resume() 配合suspend()唤醒线程被废弃

    五. 线程的优先级

    线程的优先级用处是,把任务的重要性告诉调度器,让任务的调度器,更倾向于优先级高的线程先执行,但是也存在优先级底的线程先执行的可能

    在<<java编程思想>>提到,在绝大多数情况下,都应该使线程按照默认的优先级规则执行,试图操作优先级让线程先执行,通常是一种错误

    此外,jdk中线程的优先级有是个,但是和操作系统都不能映射的很好,比如Windows系统是七个优先级,所以一般我们使用的是 MAX_PRIORITY NORM_PRIORITY MIN_PRIORITY

    • 设置优先级
    setPriority(int newPriority);
    

    其中newPriority的值由1-10 ,若不在这个范围内,抛出IllegalArgumentExeception()

    • 获取优先级
     getPriority();
    

    特性:

    • 继承性: 若A线程启动了B线程,那么B线程的优先级和A相同
    • 规则性: 当线程的优先级差距太大时,谁先执行完,和代码的先后顺序无关
    • 随机性: 虽然线程优先级高的有更大的几率优先执行完run()里面的任务,但是这是不能百分百保证的

    守护线程(daemon)

    设置为守护线程, 它肯定会随着主线程的退而退出
    java线程中有两类,一类是用户线程(非守护线程) 一类是守护线程. 它的特性就是 伴随,去守护用户线程,
    比如 java的GC(垃圾回收算法) 就是一个很称职的守护者!

    setDaemon(true);
    
    • 创建守护线程的ThreadFactory
    public class demo02 implements ThreadFactory {
    
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = newThread(r);
            thread.setDaemon(true);
            return  thread;
        }
    } 
    
    
    • 定制拥有守护线程的线程池
     ExecutorService executorService = Executors.newCachedThreadPool(new demo02());
    
    • 守护线程中的finally块

    当再没有非守护线程后,守护线程中run方法中的finally代码块是不会执行而直接退出


    参考书籍<<java编程思想>>Bruce Eckel著 <<Java多线程核心技术>> 高洪岩著

  • 相关阅读:
    2015的最后一天
    网络类型IPv4和IPv6什么意思?区别?
    2:文档编辑生成目录相关方法说明
    TCP与UDP的区别
    vs 2012 InstallShield Limited Edition Project 打包windows服务解析
    百科编辑器ueditor应用笔记
    百度编辑器Ueditor 初始化加载内容失败解决办法
    矩阵-DirectX与OpenGL的不同
    ios系统中各种设置项的url链接
    简单的优化处理 By LINQ TO SQL
  • 原文地址:https://www.cnblogs.com/ZhuChangwu/p/11150281.html
Copyright © 2020-2023  润新知