Java并发编程学习一:线程的概念以及使用

该篇文章作为自己并发学习的一个开始,首先介绍一下线程的概念以及使用。

讨论基于单核cpu进行

线程的意义

要了解线程的意义,首先先介绍一下进程,什么是进程?进程概念如下(摘自百度百科):

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

简单的理解就是一个Andorid的App(内部不引入多进程情况下)应用程序代表的一个进程,该进程在系统中拥有自己的内存空间,所有进程互相独立,互不影响。通过进程这个抽象的概念,使得操作系统中运行不同的应用程序成为了可能。进程有三个状态:

  • 运行态/执行态(Running):当一个进程在cpu上运行时,则称该进程处于运行状态。
  • 就绪态(Ready):一个进程获得了除处理机外的一切所需资源,一旦得到cpu即可运行,则称此进程处于就绪状态。
  • 阻塞态(Blocked):一个进程正在等待某一事件发生(例如IO操作)而暂时停止运行称该进程处于阻塞状态。

进程的状态在上面三个状态中转换,假设系统中总共有3个进程针对单核cpu的情况,那么一个时间片内只会有一个进程处于运行态,其他的进程分别处于就绪态和阻塞态,具体的转换过程如下图所示:

进程的概念大体介绍到这,那么既然提出来了进程,为什么又要提出线程的概念呢?试想但一个进程中存在多任务,比如在进程中输入过程中需要读取历史记录,同时还要响应用户的输入事件,那么在进程中只能串行的进行,如何进行并行就是线程出现的意义了。

线程的概念如下(摘自百度百科):

线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

扫描二维码关注公众号,回复: 4368304 查看本文章

线程是CPU调度的基本单位,在Java中一个进程至少有一个线程,多线程的概念实际上是对于系统时间片抢占的表现。对于单核cpu而言,同一时间内还是只能允许一个线程的执行,通过轮流切换使用时间片,达到并发的概念。讲到并发,并行跟并发的概念一起记录一下,用一张图表示一下两则的区别:

Erlang 之父 Joe Armstrong提供的一张解释图,可以主观理解一下。并发指的是一个处理器同时处理多个任务,这种处理方式通过轮流获得时间片方式进行,而并行是并发的子集,并行指的是多个处理器或者是多核的处理器同时处理多个不同的任务。

Java中的线程

线程在Java中的实现通过如下三种方式:

  • 继承Thread,重写run()方法。
  • 实现Runnable接口。
  • 通过Callable和Future创建。

创建简单调用Demo如下:

class ThreadTest {

    private Runnable mRunnable = () -> System.out.println("from runnable");

    private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("from Thread");
        }
    }

    private Callable<Void> mCallable = () -> {
        System.out.println("from Callable");
        return null;
    };

    void testThread() {
        new TestThread().run();
        new Thread(mRunnable).run();
        FutureTask<Void> task = new FutureTask<Void>(mCallable);
        new Thread(task).run();
    }
}
-----
输出结果:
from Thread
from runnable
from Callable

上面通过thread.start()调用启动了线程,线程在执行过程中是有生命周期的从出生到死亡,其生命周期如下图片(来自菜鸟教程)所示:

  • 新建状态:即对我们在new一个Thread的时候,此时初始化相关资源,等待start()的调用。
  • 就绪状态:在调用start()方法后,线程就如就绪状态,等待cpu进行调用。
  • 运行状态:在run()方法被调用时说明处于运行状态,此时线程获得cpu资源进行工作。
  • 阻塞状态(BLOCKED,WAITING,TIME_WAITING):在运行过程中线程让出cpu资源,并且重新进入就绪状态等待cpu调用。
  • 死亡状态:线程执行完毕。

关于各个方法对应生命周期的图如下:

接下来看下线程中的一些重要方法:

注:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

Thread.sleep(long millis)

Thread.sleep(long millis)使线程从运行状态转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。通过该方法能够使得当前线程暂停执行,让出cpu资源给其他的需要的使用,这时候状态为BLOCK状态,一定时间后进入就绪状态,准备重新获取cpu。**需要注意的是如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了CPU,但其他被同步锁挡住了的线程也无法得到执行。**在sleep()休眠时间过后,该线程也不一定会马上执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

这里需要区分sleep和wait的区别,wait和notify方法跟sychronized关键字一起配套使用,wait()方法在进入等待状态的时候,这个时候会让度出cpu资源让其他线程使用,与sleep()不同的是,这个时候wait()方法是不占有对应的锁的.

首先看下不使用同步锁时候的代码:

    void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }
	//输出结果
2018-11-09 18:52:26 PM : from Thread start run
2018-11-09 18:52:26 PM : from runnable start run
2018-11-09 18:52:26 PM : from runnable start end
2018-11-09 18:52:29 PM : from Thread end

当我们当用sleep()方法时候确实是会释放出cpu资源的。那么继续确认一下使用同步锁的情况:

    private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            synchronized (ThreadTest.class) {
                println("from Thread start run");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                println("from Thread end");
            }

        }
    }

    void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }

    private Runnable mRunnable = () -> {
        synchronized (ThreadTest.class) {
            println("from runnable start run");
            println("from runnable start end");
        }
    };
//输出结果
2018-11-09 18:54:46 PM : from Thread start run
2018-11-09 18:54:49 PM : from Thread end
2018-11-09 18:54:49 PM : from runnable start run
2018-11-09 18:54:49 PM : from runnable start end

上面的输出结果验证了我们的结论的正确性。

####Thread.join(long millis)

join()方法允许一个线程A等待另一个线程B执行完毕后再执行,也就是说具体使用如下所示:


   void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main thread executed");
    }
	
----
//输出结果
2018-11-09 18:30:14 PM : from Thread start run
2018-11-09 18:30:17 PM : from Thread end
main thread executed

可以看到主线程会等待TestThread线程执行完毕后在打印出log。主线程在调用thread.join()方法后进入了阻塞状态,等到thread执行完毕后恢复就绪状态,等待cpu重新调用。

需要注意的是join()方法实现是通过Object.wait()方法。 当main线程调用thread.join时候,main线程会获得线程对象thread的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用thread.join时,必须能够拿到线程thread对象的锁,详细可查阅该篇文章

Thread.yield()

该方法的表示该线程让出cpu资源,**从运行态直接转换为就绪态。**通俗的将就是说当一个线程A使用了这个yield()方法之后,它就会把自己CPU执行的时间让掉,cpu通过调度算法重新让A或者其它的线程运行。

示例Demo如下:

    private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 10; i++) {
                println("TestThread print" + (i + 1) + " start");
                if (i == 3) {
                    yield();
                }
            }
        }
    }

    void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }

    private Runnable mRunnable = () -> {
        for (int i = 0; i < 10; i++) {
            println("Runnable print" + (i + 1) + " start");
        }
    };
	//输出结果
2018-11-09 19:36:29 PM : Runnable print1 start
2018-11-09 19:36:29 PM : TestThread print1 start
2018-11-09 19:36:29 PM : Runnable print2 start
2018-11-09 19:36:29 PM : TestThread print2 start
2018-11-09 19:36:29 PM : Runnable print3 start
2018-11-09 19:36:29 PM : TestThread print3 start
2018-11-09 19:36:29 PM : Runnable print4 start
2018-11-09 19:36:29 PM : TestThread print4 start
2018-11-09 19:36:29 PM : Runnable print5 start
2018-11-09 19:36:29 PM : Runnable print6 start
2018-11-09 19:36:29 PM : Runnable print7 start
2018-11-09 19:36:29 PM : Runnable print8 start
2018-11-09 19:36:29 PM : Runnable print9 start
2018-11-09 19:36:29 PM : Runnable print10 start
2018-11-09 19:36:29 PM : TestThread print5 start
2018-11-09 19:36:29 PM : TestThread print6 start
2018-11-09 19:36:29 PM : TestThread print7 start
2018-11-09 19:36:29 PM : TestThread print8 start
2018-11-09 19:36:29 PM : TestThread print9 start
2018-11-09 19:36:29 PM : TestThread print10 start

当TestThread中的i等于3的时候可以看见确实TestThread让出cpu让Runnable中的多执行了一会。

Thread各种interrupt方法

在使用线程的时候,JDK为我们提供了中断线程的操作:interrupt方法,这里需要注意,通过interrupt方法只是通知线程A应该中断了,而中断与否由线程A自主控制。关于Thread中的各种interrupt主要有三个,接下来依次看下

//静态方法
 public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

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

该方法调用当前线程的isInterrupted(boolean ClearInterrupted)方法,isInterrupted(boolean ClearInterrupted)方法能够得到当前线程的状态,如果传递的是true,那么在返回当前线程状态后,它会把当前线程的interrupt状态“复位”,假设当前线程的isInterrupt状态为true,它会返回true,但过后isInterrupt的状态会复位为false。

//实例方法
 public boolean isInterrupted() {
        return isInterrupted(false);
    }


该方法为实例对象调用的方法,与静态interrupted()方法相同,唯一区别是参数传递的是false,即表示直接返回当前线程的状态

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

interrupt()方法为线程中断的方法,通过该方法能够通知对应的线程可以进入中断的状态了。如果线程在调用wait()方法,join()方法,sleep()方法后阻塞,这时候进行调用interrupt()方法,那么线程会抛出InterruptedException异常;如果线程NIO操作中阻塞,则会抛出ClosedByInterruptException异常(由于做客户端,这块不是特别了解,写错了麻烦纠正一下)。特别注意一点,调用该方法的一定是阻塞的线程来调用。

当我们在线程内部中有使用能够抛出InterruptedException异常方法时,官方建议我们通过主动调用thread.interrupt()方法进行中断线程的请求;如果线程中没有,则使用Thread.interrupted()方法以及thread.isInterrupted()方法进行判断进行中断请求。复杂的时候可以混合使用来进行中断请求的操作。

接下来就是写代码验证的时候了,首先我们测试一下调用interrupt()`方法:

public static void main(String[] args)  {
        TestThread testThread = new TestThread();
        testThread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testThread.interrupt();
    }
public static class TestThread extends Thread{

        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("interrupt occur");
            }
        }
    }
--------
//输出结果
interrupt occur

可以看到,在主线程调用testThread.interrupt()方法后,TestThread抛出了InterruptedException异常,再试试看join()方法:



    public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        TestThread1 testThread1 = new TestThread1(testThread);
        testThread.start();
        Thread.sleep(600);
        testThread1.start();
        Thread.sleep(600);
        testThread1.interrupt();
    }
    public static class TestThread1 extends Thread {
        private TestThread mThread;

        public TestThread1(TestThread thread) {
            mThread=thread;
        }
        @Override
        public void run() {
            super.run();
            try {
                mThread.join();
                System.out.println("executed after  TestThread");
            } catch (InterruptedException e) {
                System.out.println("InterruptedException for TestThread");
            }
            System.out.println("do TestThread1 job");
        }
    }
    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
        }
    }
	//输出结果
	InterruptedException for TestThread
	do TestThread1 job

可以看到,在testThread1正处于阻塞的时候,我们调用testThread1.interrupt()后,抛出了InterruptedException 异常,TestThread1没有等到TestThread执行完毕就直接走自己的任务了。

Ok,接下来再看看interrupted()以及isInterrupted()的验证过程,这里写个死循环来模拟耗时的操作:

     public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        Thread.sleep(3000);
        System.out.println("execute testThread.interrupt method");
        testThread.interrupt();
    }

    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            int i=1;
            while (!isInterrupted()) {
                i=2;
            }
            System.out.println("method for is  isInterrupted's value="+isInterrupted());
            System.out.println("method for is  isInterrupted's value2="+isInterrupted());
        }
    }
//结果
execute testThread.interrupt method
method for is  isInterrupted's value=true
method for is  isInterrupted's value2=true

上面的结果可以看到isInterrupted()方法执行获得到的一直是true,再看看interrupted()方法:

  public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        Thread.sleep(3000);
        System.out.println("execute testThread.interrupt method");
        testThread.interrupt();
    }

    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            int i=1;
            while (!isInterrupted()) {
                i=2;
            }
            System.out.println("method for is  interrupted's value="+Thread.interrupted());
            System.out.println("method for is  interrupted's value2="+Thread.interrupted());
        }
    }
//结果
execute testThread.interrupt method
method for is  interrupted's value=true
method for is  interrupted's value2=false

这里也符合我们上面得到的结论,在调用Thread.interrupted()第一次时候返回了true,java内部把标志位清除为了false,第二次再去获取就变成了false。


参考文章

猜你喜欢

转载自my.oschina.net/u/3863980/blog/2874503