zoukankan      html  css  js  c++  java
  • Java多线程基础

    目录:

    1. 进程和线程
    2. 为什么使用多线程?
    3. 多线程的创建方式
    4. Runnable与Thread两种方式比较
    5. start()与run()方法
    6. 线程的生命周期/状态转换
    7. 常用方法使用与解读
    8. 线程的优先级
    9. 守护线程

    1、进程和线程

    进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

    一个程序就是一个进程,而一个程序中的多个任务则被称为线程。

    进程是表示资源分配的基本单位,线程是进程中执行运算的最小单位,亦是调度运行的基本单位。

    2、为什么要使用多线程?

    提升效率,提升性能;发挥多核CPU的优势。

    防止阻塞:从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

    3、多线程的创建方式[细分为5种]

    • 继承Thread类,重写run方法。new一个实例。
    • 实现Runnable接口,重写run方法。作为参数传入Thread thread = new Thread(参数);来创建。
    • 匿名内部类的方式(与实现Runnable接口一样,只是形式不同 )
    • 通过并发包中Callable、Callback来创建
    • 通过线程池来创建线程

    4、Runnable与Thread两种方式比较

    继承Thread不必多说,继承后重写run方法。new一个实例调用start()方法就可以了。

    两者非要比较的话,使用Runnable较好,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。

    这里重点看下实现Runnable接口创建线程,实现Runnable接口实际上还是需要Thread类来创建线程,我们来看下Thread的构造方法API:

    Thread构造方法.jpg

    另外需要说明的是Thread.java类也实现了Runnable接口,如下图:

    Thread.java类实现Runnable接口

    那也就意味着Thread的构造函数不仅可以传入Runnable接口的对象还可以传入一个Thread类的对象,这样就可以将一个Thread类的run()方法交由其他线程来调用。

    5、start()与run()方法

    只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。

    6、线程的生命周期/状态转换

    生命周期的五种状态【简单版文字描述】:

    新建(new Thread)当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
    例如:Thread t1=new Thread();

    就绪(runnable)线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();

    运行(running)线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

    死亡(dead)
    当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

    自然终止:正常运行run()方法后终止

    异常终止:调用stop()方法让一个线程终止运行,这里说明下stop()方法虽然可以停止线程,但这个方法线程不安全,在新版本的java中已经被废弃

    堵塞(blocked)
    由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

    正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

    正在等待:调用wait()方法。(调用notify()方法回到就绪状态)

    被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)废弃

    更全的线程状态转换图如下:

    线程状态转换图.png

    7、常用方法使用与解读

    Java8在线API地址:https://docs.oracle.com/javase/8/docs/api/

    start():使线程进入“就绪”(可运行)状态,通知jvm开启新线程来执行run方法。如果多次调用了start()方法,则会出现Exception in thread "main" java.lang.IllegalThreadStateException因为start会修改当前线程的状态变量,只有状态变量是初始值时才能start。


    线程中断相关:

    stop()【废弃】:停止一个线程,可以使用Thread.stop()方法,但是他是不安全的,而且已经被废弃了呢。调用的时候还会抛出一个java.lang.ThreadDeath异常 ,但是通常情况下,此异常不需要显示的捕捉。废弃原因:因为强制让线程停止则有可能使一些请理性的工作得不到完成。另外情况就是对锁定的对象进行了“解锁”,导致 数据得不到同步的处理,出现数据不一致的问题。

    interrupt():大多数停止一个线程的操作是使用Thread.interrupt()方法,尽管名义为“中止,停止”但这个方法不会终止一个正在运行的线程,只是打了一个标记,还需要加入一个判断才可以完成线程的停止。Thread.java中提供了两个方法:

    • this.interrupted():测试当前线程是否已经中断。public static boolean interrupted()
    • this.isInterrupted():测试线程是否已经中断。public boolean isInterrupted()
    • 具体终止线程操作(来源网络):https://www.cnblogs.com/jenkov/p/juc_interrupt.html
      • 其中的return停止线程可以的,但是还是建议使用“抛异常”的方法来实现线程的停止,因为在catch块中还可以将异常上抛,使线程停止的事件得以传播。
    • 另外这两个方法的区别:
      • interrupted()无论怎么都是指正在运行的线程。而isInterrupted()就是表示指定的线程咯。
      • interrupted()会清除标记,什么意思呢?就是调用interrupt()给当前线程打一个中断标记,第一次用interrupted()会返回true但是 如果不处理,之后的调用都会返回false因为它把中断标记给清了。

    暂停线程相关:

    suspend()与resume()【废弃】:一个暂停线程,一个恢复线程到运行状态。suspend()会暂停线程,假如当前线程为关键数据结构加锁 这时被挂起那么锁将无法释放对其他线程来说造成死锁。同时也会因为线程的暂停出现数据不同步的现象。


    currentThread():该方法返回代码段正在被那个线程调用的信息。

    isAlive():判断当前线程是否处于活动的状态。活动状态是指线程已经启动尚未终止的状态。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。

    sleep():在指定毫秒内让当前正在执行的线程休眠。这个“正在执行的线程”是指this.currentThread()返回的线程。

    getId():取得线程的唯一标识。这个是自动分配的,且是唯一的。

    yield():放弃当前的CUP资源,将它让给其他的任务去占用CPU执行时间。但是放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。

    8、线程的优先级

    线程可以划分优先级,CPU优先执行优先级较高的线程对象中的任务。

    设置线程的优先级使用setPriority()方法,该方法的源码如下:

    /**
     * Changes the priority of this thread.
     * <p>
     * First the <code>checkAccess</code> method of this thread is called
     * with no arguments. This may result in throwing a
     * <code>SecurityException</code>.
     * <p>
     * Otherwise, the priority of this thread is set to the smaller of
     * the specified <code>newPriority</code> and the maximum permitted
     * priority of the thread's thread group.
     *
     * @param newPriority priority to set this thread to
     * @exception  IllegalArgumentException  If the priority is not in the
     *               range <code>MIN_PRIORITY</code> to
     *               <code>MAX_PRIORITY</code>.
     * @exception  SecurityException  if the current thread cannot modify
     *               this thread.
     * @see        #getPriority
     * @see        #checkAccess()
     * @see        #getThreadGroup()
     * @see        #MAX_PRIORITY
     * @see        #MIN_PRIORITY
     * @see        ThreadGroup#getMaxPriority()
     */
    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
    

    在Java中,线程的优先级分为【1~10】10个等级,从代码中我们也能看到,如果小于1或者大于10,则会抛出

    IllegalArgumentException异常。

    JDK中使用三个常量来预定义线程的优先级:

     /**
      * The minimum priority that a thread can have.
      */
     public final static int MIN_PRIORITY = 1;
    
    /**
      * The default priority that is assigned to a thread.
      */
     public final static int NORM_PRIORITY = 5;
    
     /**
      * The maximum priority that a thread can have.
      */
     public final static int MAX_PRIORITY = 10;
    

    线程优先级的特性:

    • 继承性:比如A线程启动B线程,则B线程的优先级与A是一样的。设置A线程的优先级为6那么B线程也就是6。
    • 规则性:线程优先级等级差距很大的时候,谁先执行完与代码的调用顺序无关。CPU尽量将资源让给优先级比较高的线程
    • 随机性:优先级高的线程不一定每次都先执行完。优先级相近越能看出随机性。

    优先级高的代码执行速度更快?

    这是一个相对的问题,因为优先级高的会占用更多的时间片,相同的任务量能够更早的完成。或者说相同时间内可以完成更多的操作。但实际上CPU处理的速度是一样的。

    9、守护线程

    守护线程是一种特殊的线程,任何一个守护线程都是整个(没错是整个)JVM中所有非守护线程的“保姆”,只要当前JVM实例中存在任何一个非守护线程没有结束,守护线程就在工作,只有当最后一个非守护线程结束时,守护线程才能随着JVM一同结束工作。Daemon的作用就是为其他线程的运行提供便利服务,守护线程最经典的应用就是GC(垃圾回收器)。

    让一个线程成为守护线程的方法是setDaemon(true);

  • 相关阅读:
    caffe常用层: batchNorm层和scale层
    简述configure、pkg-config、pkg_config_path三者的关系
    python删除list中元素的三种方法
    Leetcode 872. Leaf-Similar Trees
    Leetcode 508. Most Frequent Subtree Sum
    Leetcode 572. Subtree of Another Tree
    Leetcode 894. All Possible Full Binary Trees
    Leetcode 814. Binary Tree Pruning
    Leetcode 557. Reverse Words in a String III
    python 多维list声明时的小问题
  • 原文地址:https://www.cnblogs.com/nm666/p/10473691.html
Copyright © 2011-2022 走看看