Into high concurrency (b) Java parallel programs foundation

First, the processes and threads

In the operating system this course, the definition of process is this:

Process is a computer program run on a set of data on the activities of the system is the basic unit of resource allocation and scheduling, is the underlying operating system architecture. In early computer architecture designed for the process performed is the basic program execution entities; in contemporary computer architecture design for the thread, the thread is the process vessel.

The above definition is complete, the process is defined for all aspects, but it looks like the process is a thing invisible, in fact, we can see by looking at the process of the application process manager of the computer.

image-20191119075437166

The above process list, showing the process of multiple applications, under normal circumstances, an application occupies a process, distribution and deployment of system resources is also based process. In fact, it can be understood as a process is an application program.

So threads and processes what to do with it? Simply put, the process is the thread of "Mother", is the basic unit carrying the thread, the thread is also carrying container. For example, a building company, many employees are in their duties, orderly work with, each employee can be understood as an activity thread, sometimes more than one employee group, each group after coordination staff together to complete a job, it can be understood as groups of threads, threads within a thread group to work together to complete the work, and sometimes, the staff will be waiting to receive afternoon tea, only the current successful employees will receive a tea out of the queue , it can be understood as a thread to access the critical section, the critical section thread multiple threads waiting to complete the task leaves the critical area. Then the process can be understood as the company building this building, it is the carrier carrying the normal operation of the company (employees daily work) is.

A process is made by a combination of multiple threads, it can be said that the thread is actually a lightweight process, is the smallest unit of program execution. Now programming, emphasizing the use of multi-threaded, multi-process instead, it is because the cost of switching and scheduling between threads consumes far less than the cost of the process consumed.

Second, the thread of life cycle

There in the Java Thread class an enumeration State, within the State enumeration lists the life cycle of the thread, as follows:

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;
}

The code can be learned from the above comments, the thread of life cycle includes NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED six states.

  • NEW状态:表示线程处于刚刚创建好的状态,线程还未执行,需要等待线程调用start()方法后才表示线程开始执行。一旦执行的线程,那么就不会再回到NEW状态了。
  • RUNNABLE状态:表示线程执行所需要的资源都准备好了,正处于执行状态。
  • BLOCKED状态:表示处于RUNNABLE状态进入了阻塞状态,进入阻塞状态的原因可能是因为当前线程优先级低于其他线程,暂时尚未获取锁,线程暂时执行,直到获取到请求的锁。
  • WAITING、TIMED_WAITING状态:都表示线程进入等待状态,两者的区别在于前者是一个没有期限的等待,而后者则是有限时间内的等待。两种等待状态的线程一旦再次执行,那么又会进入到RUNNABLE状态。
  • TERMINATED状态:表示线程执行完毕后的状态,一旦进入到TERMINATED状态的线程就不会再次回到RUNNABLE状态了。

三、线程的基本操作

3.1 开启线程

开启一个新的线程很简单,在这里暂时不讨论线程池的内容,开启新线程只需要使用new关键字创建一个线程对象,并将其start()起来即可。

Thread thread = new Thread();
thread.start();

调用线程对象的start()方法以后,会开启一个新的线程去执行线程对象的run()方法,这里需要注意的是,不能直接调用run()方法,否则就是在当前线程里直接执行了run()方法,而不是在新线程里执行,这就是start()和run()方法的区别。通常创建线程对象的时候会传入一个Runnable的实现类对象,Runnable接口只有一个run()方法需要去实现,那么调用线程的start()方法就会去开启新线程执行实现类对象的run()方法。

3.2 结束线程

通常来说,新建线程在在完成执行任务后会自动关闭,无需人工理会,但是在某些情况下,可能为了减少不必要的执行流程,会手动去关闭线程。从JDK源码来看,线程类Thread提供了一个停止线程的方法stop(),该方法可以停止线程的执行,使用起来十分方便,但是它已经被标注为“废弃”了,也就是说不推荐使用了。这是为什么呢?原因是因为stop()方法过于暴力,强行将执行中的线程停止,这样就有可能会造成内存中数据不一致的现象。

举个例子,比如新建线程的主要执行流程就是给user对象设置ID和名称,创建新线程后,设置完ID就强行停止了线程,那么内存中的user对象的两个属性中名称属性可能就是默认值,并没有成功设置,这样其他线程读取该数据就和预想的不一致了。

Thread类的stop()方法会强行停止线程,也就释放该线程持有的锁,释放锁后其他线程就有机会获取该锁,从而读取到该对象,那么读取到的数据就是不完整的数据。

3.3 中断线程

线程中断的解决方案要优于线程终止,线程中断不会像线程终止一样,会立马结束线程的后续执行流程,前者更像是得到了一个通知,通知他可以退出执行了,当线程接受这样的通知以后,会在一个合适的时机退出线程,并且不会造成脏数据问题。这个特点将线程中断和线程终止区别开来,是一个很重要的特点。

在Thread类中,有三个方法与线程中断息息相关:

public void interrupt()
public boolean isInterrupted()
public static boolean interrupted()
  • 第一个方法的作用是设置线程的中断标志位,也就是通知线程需要中断了。
  • 第二个方法的作用是通过检查中断标志位,来判断当前线程是否被中断。
  • 第三个方法是一个静态方法,也是用来检查线程的是否被中断,但是和第二个方法的区别就是会清除线程的中断标志位。

以上三个方法的方法体很简单,读者可以自行前往Thread类进行阅读。

下面的案例用来测试线程中断,代码如下:

public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("test");
                Thread.yield();
            }
        });
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
}

仔细观察代码,发现最后一行使用了线程中断方法,将thread线程进行中断,将代码运行起来之后发现,在控制台一直打印着“test”,完全没有停止下来的意思。分析原因得知,调用线程对象的interrupt()方法仅仅是设置了中断标志位,并不会去主动中断线程,那么上文中所说的通知线程中断后,会在合适的时机退出线程,那么何时是合适的时机呢?这就需要人工介入了。

public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Interrupted");
                    break;
                }
                System.out.println("test");
                Thread.yield();
            }
        });
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
}

上述代码在合适的位置加入了一个判断,判断当前线程的中断标志位是否被设置为中断,从而决定是否要中断线程,这就是所说的何时的时机。

这里需要注意一点的是,Thread.sleep()方法如果发生异常,那么当前线程设置的中断标志位会被清除掉,如果要捕获此类异常,那么需要重新设置中断标志位,也就是通知线程需要中断了。

3.4 等待和通知

为了适应线程间的协作能力,JDK的Object类提供了wait()和notify()方法,即等待方法和通知方法,等待方法指的是调用某个类对象的wait()方法后,当前线程进入到等待状态,等待直到其他线程内的同一对象调用了notify()方法为止,其他线程将通知当前线程继续执行后续流程。由于这两个方法位于Object类中,那么就代表任何对象都是可以调用这两个方法。

当某个线程调用了object对象的wait()方法,那么该线程就进入了等待object对象的队列中,由于多个线程执行到同一位置,都会进入到等待object对象的队列中,都在等待其他线程调用object对象的notify()方法,当调用了notify()方法后,并不是所有的等待线程都会继续执行后续流程,而是其中的某个线程收到通知后继续执行后续流程。notify()方法是从等待队列中随机选取一个线程去激活,并不是所有的线程都能收到通知。当然,Object类也提供了notifyAll()方法,那么它的作用就是通知所有的等待线程继续后续流程。

这里有个细节需要注意,那就是调用wait()和notify()首先都必须包含在synchronized语句中,因为它们的调用必须获取到目标对象的锁。下图展示了wait()方法和notify()方法的工作流程细节。

image-20191122073456465

wait()方法和sleep()方法都可以让线程等待,但是二者还是有却别的:

  • wait()方法使线程进入等待,但是可以重新被唤醒
  • wait()方法会释放目标对象的锁,而sleep()方法不会释放任何资源
3.5 挂起和继续

挂起(suspend)和继续(resume)是一对JDK提供的线程接口,挂起可以将当前线程暂停,直到对应线程执行了继续接口,否则将不会释放目标对象的资源,这一对方法已经被JDK标注为“废弃”,不再被推荐使用。

3.6 等待线程结束(join)和谦让(yeild)

当一个线程的输入可能非常依赖另外一个或者多个线程的输出,此时,这个线程就必须等待被依赖的线程执行完毕,才能继续执行。对于这种需求,JDK提供了join()方法来实现这个功能。JDK提供了两个join()方法,方法签名如下所示:

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

方法一表示目标线程调用后,那么当前线程就会一直等待,直到目标线程执行完毕。方法二设置了一个最大等待时间,如果超过这个最大等待时间,那么当前线程就不会等待目标线程执行完毕,就会继续执行后续的流程。

public class JoinThread extends Thread {

    private volatile static int i = 0;

    @Override
    public void run() {
        for (i = 0; i < 100000; i++) ;
    }

    public static void main(String[] args) throws InterruptedException {
        JoinThread joinThread = new JoinThread();
        joinThread.start();
        joinThread.join();
        System.out.println(i);
    }
}

上述代码中,如果不使用join()方法,那么打印出来的i值为0或者很小很小的值,使用了join()方法后,那么始终会等待新线程的执行完毕后继续执行,此时打印出来的i值始终是100000。

线程谦让是指线程主动让出CPU,让出CPU后还会进入到资源争夺中,至于还有没有机会再争夺到资源,那就不一定了。JDK提供了yeild()方法来实现此功能,目的是为了让低优先级的线程尽量少占用过多资源,尽量让出资源给高优先级的线程。

了解更多干货,欢迎关注我的微信公众号:爪哇论剑(微信号:itlemon)
Public micro-channel number - Java swordfight -itlemon

Published 73 original articles · won praise 84 · views 470 000 +

Guess you like

Origin blog.csdn.net/Lammonpeter/article/details/103196807