β

Java必知必会之(四)—多线程全揭秘(上)

Harries Blog™ 28 阅读

本文旨在用最通俗的语言讲述最枯燥的基本知识。

全文提纲:

1. 线程 是什么?(上)

2.线程和 进程 的区别和联系(上)

3.创建 多线程 的方法(上)

4.线程的 生命 周期(上)

5.线程的控制(上)

6. 线程同步 (下)

7. 线程池 (下)

8.ThreadLocal的基本用法(下)

9.线程 安全 (下)

1.线程是什么

线程是进程中的一个执行流程,是被系统独立调度和分派的基本单位。

线程是什么?进程是什么?

这么说可能有点懵逼,举个栗子吧:

A工厂是一个生产 汽车 的工厂,今天员工张三去送货,员工李四去进货。这里的

  1. A工厂是一个进程(当然荒废的没有生命迹象的工厂不能算进程了)
  2. 员工张三去送货 是一个线程
  3. 员工李四去进货 也是一个线程

从例子可以看出

进程是指运行中的程序(没运行的程序,系统是不会为之分配资源的),每个进程都有自己独立的内存 空间 ,当一个程序进入内存运行时,程序内部可能包含多个程序执行流,这个程序执行流就是一个线程。

几乎所有 操作系统 都支持多线程 并发 ,就像我们平时上班用的电脑,我们可能习惯打开 eclipse 代码 ,同时打开网易 音乐听课,而且还要打开有道 翻译 时刻准备着把我们的中文转成英文…

可见我们的电脑可以支持多个应用程序同时执行的,但实际上,而对于每个CPU来说,它在一个 时间 点内,只能执行一个程序,也就是一个进程,那为什么我们同时打开这么多程序运行没问题呢?

那是因为现代电脑都不止一个CPU啦。当然这个是一个原因。

最主要的是因为在程序运行过程中,CPU在不同程序之间高速的来回切换执行,因此所谓的“并发执行”实际上并不是多个程序在同时执行,而是系统对程序的执行做了调度,让视觉上看起来是同时执行了。

所以线程中的并发:

是指多个进程被CPU快速的轮换执行,而不是同时执行

2. 线程和进程的区别

通过上面的原理讲述已经能看出区别了,最主要有2点

  1. 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
  2. 线程是进程中的一个执行流程,一个进程可以包含多个线程

3. 多线程的创建

1. 继承Thread类创建线程:

public class ThreadTest  extends Thread {
    @Override
    public void run() {
        // 业务逻辑
        super.run();
    }

    public static void main(String[] args) {
        new ThreadTest().run();
        new ThreadTest().run();
    }
}
复制代码

2. 实现Runnable接口

public class ThreadTest  implements Runnable {
    @Override
    public void run() {
         //业务逻辑
    }

    public static void main(String[] args) {
        new ThreadTest().run();
        new ThreadTest().run();
    }
}
复制代码

3. 使用Callable和Future创建

Callable接口是jdk5之后的新接口,它提供了一个call方法作为线程执行体,和thread的run方法类似,但是它的功能更强大:

  • 它可以有返回值
  • 它可以声明抛出异常
    因此也可以像Runnable一样,创建一个Callable对象作为Thread的 tar get,而实现它的call方法作为执行体.
    同时jdk5提供了Future接口来代表Callable接口里的call方法返回值,并为Future接口提供了一个FutureT ask 实现类,该实现类实现了Future接口,并实现了Runable接口,它有以下几个方法:
  1. boolean cancal(boolean mayInterruptRunning):试图取消该Future里关联的Callable任务
  2. V get():返回Callable任务里call()方法的返回值,调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值
  3. V get(long timeout,TimeUnit unit):
  4. boolean isCancel():如果在Callable任务正常完成前被取消,则返回true
  5. boolean isDone():如果Callable任务已完成,则返回true
public static void main(String[] args) {

        //1)创建一个Callable实现类,并实现call方法
        //2)用FutrueTask来包装类的实例
     FutureTask ft=new FutureTask<>(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            System.out.println("执行了");
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }

            return 12;
        }
    });
     //使用FutureTask对象作为target来创建并且启动线程
     new Thread(ft).start();

     //阻塞方式获取线程返回值
     try {
        System.out.println("返回值:"+ft.get());
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (ExecutionException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
     //带有超时方式获取线程返回值
     try {
        System.out.println("返回值:"+ft.get(2,TimeUnit.SECONDS));
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    }
复制代码

4. 线程的生命周期

线程创建之后,不会立即处于运行状态,根据前面对并发的定义理解:即使他启动了也不会永远都处于运行状态,如果它一直处于运行状态,就会一直占据着CPU资源,线程之间的切换也就无从谈起了。因此,线程是有生命周期的,他的生命周期包括以下几种状态:

Java必知必会之(四)---多线程全揭秘(上)
  1. 新建(NEW)
  2. 就绪(Runnable)
  3. 运行(Running)
  4. 阻塞(Blocked)
  5. 死亡(Dead)

1. 新建状态

当在程序中用new创建一个线程之后,它就处于新建状态,此时它和程序中其它对象一样处于初始化状态(分配内存、初始化成员变量)。

2.就绪状态

当程序调用了start方法之后,程序就处于就绪状态,jvm会为它创建方法调用栈和程序计数器,此时的线程状态为可运行状态,并没有运行,而是需要线程 调度器 的调度决定何时运行。

3. 运行状态

当就绪的线程获得CPU之后,就会执行线程执行体(run方法),这时候线程就处于了运行状态。

4.阻塞状态

处于运行的状态的线程,除非执行时间非常非常非常短,否则它会因为系统对资源的调度而被中断进入阻塞状态。操作系统大多采用的是抢占式调度策略,在线程获得CPU之后,系统给线程一段时间来处理任务,当到时间之后,系统会强制性剥夺线程所占资源,而分配别的线程,至于分配给谁,这个取决于线程的优先级。

5.死亡状态

处于运行状态的线程,当它主动或者被动结束,线程就处于死亡状态。至于结束的形式,通常有以下几种:

  1. 线程执行完成,线程正常结束
  2. 线程执行过程中出现异常或者错误,被动结束
  3. 线程主动调用stop方法结束线程

5.线程的控制

Java提供了线程在其生命周期中的一些方法,便于 开发者 对线程有更好的控制。

主要有以下方法:

  1. 等 待:join()
  2. 后 台:setDeamon()
  3. 睡 眠:sleep()
  4. 让 步:yield()
  5. 优先级:setPriority()

1.线程等待

当某个线程执行流中调用其他线程的join()方法时,调用线程将被阻塞,知道被join()方法加入的join()线程执行完成为止。

乍一看,怎么也理解不了,这句话,我们来写一个程序 测试 一下:

public class ThreadTest  extends Thread {
    @Override
    public void run() {
        System.out.println(getName()+"运行...");
        for(int i=0;i<5;i++){
            System.out.println(getName()+"执行:"+i);
        }
    }
    public ThreadTest(String name){
        super(name);
    }
    public static void main(String[] args) {
        //main方法--主线程
        //线程1
        new ThreadTest("子线程1").start();
        //线程2
        ThreadTest t2=new ThreadTest("子线程2");
        t2.start();

        try {
          t2.join(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        //线程3
        new ThreadTest("子线程3").start();
    }
}
复制代码

看输出结果:

子线程1运行...
子线程2运行...
子线程2执行:0
子线程2执行:1
子线程2执行:2
子线程2执行:3
子线程1执行:0
子线程2执行:4
子线程1执行:1
子线程1执行:2
子线程1执行:3
子线程1执行:4
子线程3运行...
子线程3执行:0
子线程3执行:1
子线程3执行:2
子线程3执行:3
子线程3执行:4
复制代码

可以看到,线程1和2在并发执行着,而线程3则在他们都执行完之后才开始。

由此可知:

join()方法调用之后,后面的线程必须等待前面执行完之后才能执行,而不是并发执行

2.线程转入后台

当线程调用了setDaemon(true)之后,它就转入为后台线程,为前台线程提供服务,而当前台所有线程死亡时,后台线程也会接受到 JVM 的通知而自动死亡。

ThreadTest t2=new ThreadTest("子线程2");
//这是为后台线程,但必须在start前设置,因为前台线程死亡JVM会通知
//后台线程死亡,但接受指令到响应需要时间。因此要自爱start前就设置
        t2.setDaemon(true);
        t2.start();
复制代码

3. 线程睡眠

当需要某个处于运行状态的线程暂停执行并且进入阻塞状态时,调用Thread.sleep既可。

4.线程让步

当需要某个处于运行状态的线程暂停执行并且进入就绪状态,调用

Thread.yield()即可

5.线程优先级

前面说到,系统分配CPU给哪个线程的执行,取决于线程的优先级,因此每个线程都有一定的优先级,优先级高的线程会获得更多的执行机会,默认情况下,每个线程的默认优先级都与创建它的父线程优先级一致。

当我们需要某个线程或者更多的执行机会时,调用

Thread.currentThread().setPriority(int newPriority);

方法即可,newPriority的范围在1~10。

关于多线程的揭秘,上集都讲到这里,更高级的使用多线程,尽在下集 请关注我哦。

觉得本文对你有帮助?请分享给更多人

关注「编程无界」,提升装逼技能

Java必知必会之(四)---多线程全揭秘(上)

原文

https ://juejin.im/post/5b8e976fe51d4538a515d8b5

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。 PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处: Harries Blog™ » Java必知必会之(四)—多线程全揭秘(上)

作者:Harries Blog™
追心中的海,逐世界的梦
原文地址:Java必知必会之(四)—多线程全揭秘(上), 感谢原作者分享。

发表评论