JAVA多线程学习笔记(一): 多线程的基础概念以及Thread类常用方法介绍

博主最近在学习高洪岩编写的《Java多线程编程核心技术》,以下是我的学习笔记:

文章目录


#1.关于java多线程的一些重要概念
##1.1 线程、进程
###1.1.1 基本定义

  • 进程:计算机上所有可运行的软件,通常也包括操作系统,被组织成若干顺序进程,简称进程。一个进程就是一个正在执行程序的实例,包括程序计数器、寄存器和变量的当前值。
  • 线程:轻量级进程(在一个进程中再有一类进程)(轻量级,意味着更容易创建,创建速度更快,也更容易撤销)

###1.1.2 区别

  • 进程是计算机资源分配的基本单位,线程是CPU调度的基本单位
  • 进程有独立的地址空间,线程共享进程的地址空间
  • 进程的开销比线程大
  • 进程与进程之间为竞争关系,同一进程中的线程为合作关系

##1.2 多线程、并发、并行
###1.2.1 多线程
举例:比如用浏览器,同时进行浏览网页、播放视频、下载资源、听音乐等操作

###1.2.2 并发(concurrency)
同时处理多个事务的能力。并发是把CPU运行时间划分成若干个时间段,每个时间段再分配给各个线程执行,当一个线程在运行时,其它线程处于挂起状。从宏观角度是同时进行的,但从微观角度并不是同时进行

###1.2.3 并行(parallel)
多个cpu实例或者多台机器同时处理一段逻辑(并行是同一时刻当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,是真正意义上的不同线程在同一时刻同时执行

###1.2.4 区别

  • 并发就像一个人(CPU)喂两个小孩(程序)吃饭,表面上是两个小孩在吃饭,实际是一个人在喂。
  • 并行就是两个人喂两个小孩子吃饭。

##1.3 同步、异步
###1.3.1 同步
调用一个函数,要等这个函数执行完,才能进入下一步。(有等待的过程,函数之间有前后的关系)

###1.3.2 异步
调用一个函数,不用等待该函数执行,可以继续调用其他函数。

###1.3.3 举例
打个比方,比如我们去购物,如果你去商场实体店买一台空调,当你到了商场看中了一款空调,你就想售货员下单。售货员去仓库帮你调配物品。这天你热的实在不行了。就催着商家赶紧给你配送,于是你就等在商场里,候着他们,直到商家把你和空调一起送回家,一次愉快的购物就结束了。这就是同步调用
不过,如果我们赶时髦,就坐再家里打开电脑,在网上订购了一台空调。当你完成网上支付的时候,对你来说购物过程已经结束了。虽然空调还没有送到家,但是你的任务都已经完成了。商家接到你的订单后,就会加紧安排送货,当然这一切已经跟你无关了,你已经支付完成,想什么就能去干什么了,出去溜达几圈都不成问题。等送货上门的时候,接到商家电话,回家一趟签收即可。这就是异步调用
(注:以上内容摘自Java高并发程序设计)

##1.4 线程安全
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

#2. 线程状态
##2.1 状态
查看源码,JAVA线程一共有6种状态

状态 含义
NEW 尚未启动的线程处于这种状态
RUNNABLE 正在JAVA虚拟机中执行的线程处于这种状态
BLOCKED 阻塞并等待某个监视器锁的线程处于这种状态
WAITING 无限期地等待另一个线程来执行某一特定操作的线程处于这个状态
TIMED_WAITING 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态
TERMINATED 已退出的线程处于这种状态

JAVA 源码:

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

###2.1.1 NEW、RUNNABLE、TERMINATED
这三个状态比较好理解,就是从创建到运行到最后完成的生命流程

###2.1.2 WAITING、TIMED_WAITING、BLOCKED
这三个状态,相对比较容易混乱,这里进行一下区分:

  • BLOCKED:是出现在某一个线程在等待锁的时候。其他的线程占有了锁,而当前的线程等待其他线程释放锁。

  • WAITING:等待,这里看起来跟上一个blocked被堵塞没有什么不同,查看源码,我们可以看到这个状态对应的三种情况:发现都是一个等待被其他线程唤醒的情况。BLOCKED是等待其他线程释放进程,而WAITING是等待其他线程用notify()等方式唤醒线程。

     /**
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     */
    
  • TIMEWAITIG:直接翻译,就是时间等待的意思,我们查看它的源码,可以发现就像它的字面翻译一样,它就是处于一个等待时间执行完的状态

     /**
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    

###2.2 线程状态的转换
这里用一副图介绍一下:
这里写图片描述
再举个例子解释一下:

  • 比如现在有三个线程,同时到了到了同步块之前,其中两个进程调用了wait(),则一个线程保持了RUNABLE的状态,但另外两个线程进入了WAITING的状态,第一个线程完成了同步块的执行,并调用了notifyall()的函数,此时另外两个线程被唤醒,其中一个线程进入同步块,状态位RUNNABLE状态,另一个进入BLOCKED状态

#3.Thread类介绍
##3.1 实现多线程的两种方法

###3.1.1 继承Thread类,并实现了run方法

public class MyThread extends Thread {
    @Override
    public void run() {
	    super.run();
    }
}

###3.1.2 实现Runable接口,实现run方法

public class MyThread1 implements Runnable{
    @Override
    public void run() {
    }
}

###3.1.3 创建对应的线程:

// MyThread 继承了 Thread类
MyThread myThread = new MyThread();
myThread.start();
	
// MyThread 实现了 Runnable接口
Runnable myThread1 = new MyThread1();
Thread thread = new Thread(myThread1);
thread.start();

这里就有一个问题,为什么两种方法调用的时候不一样,我们这里查看 Runnable 的源码

// Runnable.class
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

只有一个方法,且为抽象函数,因此实现该接口,实际上只是实现了一个方法的普通类,并非新的线程。我们再看 Thread 类:其本身就实现了Runnable的接口,查看它的源码,发现有几个native方法,(关于native,https://blog.csdn.net/Applying/article/details/81572167,我得上一篇博客里面有提及,可能能帮助您理解一下)也有部分是Java实现的方法。这里查看Thread的start方法

// Thread.class
private ThreadGroup group;
public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

它通过将向ThreadGroup中加入一个新的进程,来创建一个新的进程。
这里就要注意一下,Thread也有run方法,但

// Thread.class
private Runnable target;
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

这里注意一点,在一些面试题里也会问到:在Thread中直接调用run函数,是不能启动新的线程的。具体的原因,上面也已经解释过了

###3.1.4 两种方法的对比
使用继承Thread类的方式来开发多线程应用程序在设计上有局限性的,因为JAVA的单根继承,所以推荐使用实现Runnable接口的方式进行
##3.2 Thread类常用方法
###3.2.1 start()
开启一个新的线程,并将线程加入当前的线程组中

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

这里要注意一点,多个顺序执行的start()方法,顺序不代表线程启动的顺序。如下面的例子,执行的结果,并不会是“0 1 2 3 4 5 6 7 8 9”,某次执行的结果是:“1 4 2 6 0 5 3 9 7 8”

public class MyThread extends Thread {
    private int i;

    @Override
    public void run() {
	    super.run();
	    System.out.println(i);
	}

    public MyThread(int i) {
	    super();
	    this.i = i;
    }
}

public class AA {
    public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
    		MyThread myThread = new MyThread(i);
	    	myThread.start();
	    }
    }
}

###3.2.2 currentThread()
返回当前的线程,获取当前线程的实例,并进行各种操作,例如定义线程名,获取线程名等。

###3.2.3 sleep()
在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行),执行完的线程进入TIMEWAITING的状态,等待时间走完再次被唤醒

###3.2.4 getId()
获得线程的唯一标识

###3.2.5 停止线程

Java有三种方法终止正在运行的线程:

  • 使用退出标志,使线程正常退出,即run方法完成后线程终止
  • stop(),这是一种已经被弃用作废的方法,不推荐。这种方法是暴力停止的方式,可能使一些请理性的工作得不到完成
  • 使用interrupt()中断线程

###3.2.6 interrupt()
通过查看源码,我们可以发现调用该方法仅仅是在当前线程中打了一个停止的标志,并不是真的停止线程。而且还有两个配套的方法

  • this.interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志置为false的功能

  • this.isInterrupted():测试线程是否已经是中断状态,但不清除状态标记

  • 意味着,一个被标记的线程,连续两次执行interrupted(),第二次执行的时候,会返回false,因为第一次执行的时候,标志已经被清除。具体原因,我们可以看下面的源码,两个函数的实现,差别只有在是否重置停止标志

     public static boolean interrupted() {
         return currentThread().isInterrupted(true);
     }
    
     public boolean isInterrupted() {
         return isInterrupted(false);
     }
    
     /**
      * Tests if some Thread has been interrupted.  The interrupted state
      * is reset or not based on the value of ClearInterrupted that is
      * passed.
      */
     private native boolean isInterrupted(boolean ClearInterrupted);
    

###3.2.7 如何正确停止线程
既然stop()不推荐,interrupt()只能打标记,那如何正确停止线程呢。常见的有两个方法

  • 异常法:利用interrupt()来对线程进行标记,并通过两个判断状态的函数进行判断,如果true,抛出异常,并在catch块中对异常信息进行相关的处理
  • 使用return停止线程:思路类似于异常法,利用interrupt()进行打标记,然后判断并返回,但更推荐上面的方法,因为使用异常流可以更好、更方便地控制程序的运行流程。

###3.2.8 暂停线程
suspend()方法暂停线程,resume()方法恢复线程的执行。但这两个方法,已经被弃用了,因为在暂停的时候,并不会释放资源,容易造成死锁,我们可以看到源码中的说明:

/**
* @deprecated   This method has been deprecated, as it is
 *   inherently deadlock-prone.  If the target thread holds a lock on the
 *   monitor protecting a critical system resource when it is suspended, no
 *   thread can access this resource until the target thread is resumed. If
 *   the thread that would resume the target thread attempts to lock this
 *   monitor prior to calling <code>resume</code>, deadlock results.  Such
 *   deadlocks typically manifest themselves as "frozen" processes.
 *   For more information, see
 *   <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
 *   are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
 */
@Deprecated
public final void suspend() {
    checkAccess();
    suspend0();
}

那应该如何暂停跟启动呢?个人觉得方法有两个:

  • 使用sleep(),让线程暂停一段时间,等待时间过后自动唤醒,不过sleep也不会释放资源
  • 使用wait(),让方法暂停,并释放资源,等待其他事情完成后,其他线程利用notify()将其唤醒,结束暂停状态

###3.2.9 yield()
作用为放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,就获取CPU时间片

###3.2.10 线程的优先级
setPriority()方法可以设置优先级。线程的优先级,一共分为1~10这10个等级,不在范围内的,会抛出异常。而且有3个常量预置定义优先级的值:

/**
 * 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;

关于优先级问题注意几个点:

  • 线程优先级具有继承性
  • CPU尽量将资源让给优先级比较高的线程
  • 优先级具有随机性,也就是优先级较高的线程不一定会每一次都先执行完

后面的笔记内容包括

  • synchronized关键字
  • volatile关键字
  • 线程间通讯
  • 高级多线程控制类

猜你喜欢

转载自blog.csdn.net/Applying/article/details/81812448