• 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);

  • 相关阅读:
    leetcode Move Zeroes
    leetcode Same Tree
    leetcode range sum query
    leetcode Invert Binary Tree
    leetcode【sql】 Delete Duplicate Emails
    mac编译PHP报错 configure: error: Please reinstall the libcurl distribution
    Linux添加系统环境变量的两种方法
    Mysql获取去重后的总数
    MySQL查询order by相减select相减的Sql语句
    修改maven本地仓库路径
  • 原文地址:https://www.cnblogs.com/nm666/p/10473691.html
Copyright © 2020-2023  润新知