并发编程一、多线程的意义和使用

一、相关概念

1. 影响服务器吞吐量的因素

  硬件方面:CPU、内存、磁盘、网络
  软件层面:硬件资源是前提,软件层面的配置是为了最大化的利用硬件资源,如配置 线程数量、JVM内存分配大小、网络通信机制(BIO/NIO/AIO)、磁盘IO
从计算机本身的角度来考虑的话,一个请求的访问到处理最终到返回,性能瓶颈只会是:CPU、文件IO、网络IO、内存、等因素。而一台计算机中这些纬度是有性能瓶颈的 ,如果某个资源消耗过多,通常会造成系统的响应速度较慢,所以增加一台机器使得数据库的IO和CPU资源独占一台机器从而增加性能。

各个资源的消耗原因
CPU/IO/内存
1. CPU,主要是上下文的切换,因为每个CPU核心在同一时刻只能执行一个线程,而CPU的调度有几种方式,比如抢占式和轮询等,以抢占式为例,每个线程会分配一定的执行时间,当达到执行时间、线程中有IO阻塞或者有高优先级的线程要执行时。CPU会切换执行其他线程。而在切换的过程中,需要存储当前线程的执行状态并恢复要执行的线程状态,这个过程就是上下文切换。比如IO、锁等待等场景下也会触发上下文切换,当上下文切换过多时会造成内核占用比较多的CPU。
2. 文件IO,比如频繁的日志写入,磁盘本身的处理速度较慢、都会造成IO性能问题
3. 网络IO,带宽不够
4. 内存,包括内存溢出、内存泄漏、内存不足
实际上不管是应用层的调优也好,还是硬件的升级也好。其实无非就是这几个因素的调整。

2. 进程与线程

对比 进程 线程
定义 进程是程序运行的一个实体的运行过程,是系统进行资源分配和调配的一个独立单位 线程是进程运行和执行的最小调度单位
包含关系 一个进程内有1到N个线程 线程是进程的一部分,也被称为轻量级的进程
系统开销 创建撤销切换开销大,资源要重新分配和收回 仅保存少量寄存器的内容,开销小,在进程的地址空间执行代码
拥有资产 资源拥有的基本单位 基本上不占资源,仅有不可少的资源(程序计数器,一组寄存器和栈)
调度 资源分配的基本单位 独立调度分配的单位
安全性 进程间相互独立,互不影响 线程共享一个进程下面的资源,可以互相通信和影响
地址空间 系统赋予的独立的内存地址空间 由相关堆栈寄存器和和线程控制表TCB组成,寄存器可被用来存储线程内的局部变量

3. 并发和并行

  并行:指两个或多个事件在同一时刻发生

  并发:指两个或多个事件在同一时间间隔内发生。以单核CPU的电脑为例,如果它是以多线程运行的,CPU是通过不断分配时间片的方式来实现线程切换,只是线程之间切换的速度太快我们难以察觉。

二、线程的应用

1. 线程创建

  在Java中,有多种方式来实现多线程。继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现带返回结果的多线程。
  继承Thread类、

public class MyThread extends Thread {

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + " is running");

    }

    public static void main(String[] args) {

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

    }

}

  实现Runnable接口、

public class MyRunnable implements Runnable {

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + " is running");

    }

    public static void main(String[] args) {

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

}

  使用ExecutorService、Callable、Future实现带返回结果的多线程

import java.util.concurrent.*;

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {

        String str = Thread.currentThread().getName() + " is running";
        return str;
    }

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(1);
        Future<String> future = pool.submit(new MyCallable());

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        pool.shutdown();

    }

}

三、线程的生命周期

1. 代码演示线程的几种状态

  首先分别创建几种情况的线程,然后使用jpsjstack命令分别查看该进程pid下的线程的状态。
  4个不同的线程:Thread_Status_01:休眠一定时间;Thread_Status_02: 等待;Thread_Status_03Thread_Status_04: 两个线程同时抢占一个对象锁,所以其中会有一个线程一定没有抢到锁;

import java.util.concurrent.TimeUnit;

public class ThreadStatus {

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {

                while (true) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }, "Thread_Status_01").start();

        new Thread(() -> {
            while (true) {
                synchronized (ThreadStatus.class) {
                    try {
                        ThreadStatus.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "Thread_Status_02").start();


        BlockedDemo blockedDemo = new BlockedDemo();

        new Thread(() -> {
            blockedDemo.test();
        }, "Thread_Status_03").start();

        new Thread(() -> {
            blockedDemo.test();
        }, "Thread_Status_04").start();

    }


}

class BlockedDemo {

    public void test() {

        synchronized (this) {

            while (true) {

                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }

    }

}

  命令行或Intellij Idea中右键Open In Terminal在IDE中打开命令行,输入jps查看demo对应的进程pid,可以看到ThreadStatus所对应进程pid为15516

  之后,使用JVM工具jstack来查看该pid下的信息,找出定义的4个线程及其对应状态;windows下也可以使用jvisualvm命令来打开Java VisualVM可视化工具找到对应pid的线程信息,导出线程 Dump来查看线程信息

E:\..\..\..\bigshen-threads-boot\src\main\java\com\bigshen\threads>jstack 15516

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.111-b14 mixed mode):

"DestroyJavaVM" #16 prio=5 os_prio=0 tid=0x00000000030e3800 nid=0x3ee8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread_Status_04" #15 prio=5 os_prio=0 tid=0x000000001fbfa000 nid=0x3f8c waiting for monitor entry [0x00000000204ff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bigshen.threads.BlockedDemo.test(ThreadStatus.java:61)
        - waiting to lock <0x000000076bc86e90> (a com.bigshen.threads.BlockedDemo)
        at com.bigshen.threads.ThreadStatus.lambda$main$2(ThreadStatus.java:44)
        at com.bigshen.threads.ThreadStatus$$Lambda$3/769287236.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

"Thread_Status_03" #14 prio=5 os_prio=0 tid=0x000000001fbef000 nid=0x168c waiting on condition [0x00000000203fe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.bigshen.threads.BlockedDemo.test(ThreadStatus.java:61)
        - locked <0x000000076bc86e90> (a com.bigshen.threads.BlockedDemo)
        at com.bigshen.threads.ThreadStatus.lambda$main$1(ThreadStatus.java:40)
        at com.bigshen.threads.ThreadStatus$$Lambda$2/2046562095.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

"Thread_Status_02" #13 prio=5 os_prio=0 tid=0x000000001fbee800 nid=0x38d0 in Object.wait() [0x00000000202ff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b823090> (a java.lang.Class for com.bigshen.threads.ThreadStatus)
        at java.lang.Object.wait(Object.java:502)
        at com.bigshen.threads.ThreadStatus.lambda$main$0(ThreadStatus.java:28)
        - locked <0x000000076b823090> (a java.lang.Class for com.bigshen.threads.ThreadStatus)
        at com.bigshen.threads.ThreadStatus$$Lambda$1/214126413.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

"Thread_Status_01" #12 prio=5 os_prio=0 tid=0x000000001ee5f800 nid=0x3dfc waiting on condition [0x000000001f9ff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.bigshen.threads.ThreadStatus$1.run(ThreadStatus.java:15)
        at java.lang.Thread.run(Thread.java:745)

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001edab800 nid=0xd68 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   
....


  可以看到:线程Thread_Status_01TIMED_WAITING 超时等待状态、线程Thread_Status_02WAITING 等待状态、
  线程Thread_Status_03TIMED_WAITING 超时等待状态,表明线程3已结执行了sleep方法,获得了锁、
  线程Thread_Status_04BLOCKED 阻塞状态,因为线程4和线程3再抢夺同一个对象锁,线程3获得了锁,线程4阻塞。

2. 线程的生命周期

  Java线程既然能够创建,势必也能被销毁,所以线程是存在生命周期的。
  线程一共有6种状态新建(NEW)运行(RUNNABLE)超时等待(TIMED_WAITING)等待(WAITING)阻塞(BLOCKED)终止(TERMINATED)

3. 线程的启动

  线程的启动并不是直接调用run()方法,而是调用start()方法,源码中可以看到真正调用的是native的start0()方法,底层为C语言的逻辑实现。

  private native void start0(); 

4. 线程的终止

  线程的终止也并不是简单的调用stop()命令,首先它是一个过期的方法,根本原因是stop方法在结束一个线程时并不会保证线程的资源正常释放,因此会导致程序可能出现一些不确定的状态。
  要去优雅的中断一个线程,在线程中提供了一个interrupt()方法。底层调用的是native的interrupt0()方法,同样为C语言实现。

  private native void interrupt0(); 

  下面以三个demo来演示线程的终止。
  样例1:主线程创建线程Thread_end_demo,该线程内部根据线程是否是终止状态来决定是否继续执行;然后主线程在休眠100毫秒后终止Thread_end_demo线程。

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class ThreadEndDemo implements Runnable {

    @Override
    public void run() {

//        while (true) {
        while (!Thread.currentThread().isInterrupted()) {
            // do something
            System.out.println(Thread.currentThread().getName() + " ----  " + Thread.currentThread().isInterrupted() + " -----  " + new Date());
        }

    }

    public static void main(String[] args) {

        Thread thread = new Thread(new ThreadEndDemo(), "Thread_end_demo");

        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("set child thread interrupt at" + new Date());
        thread.interrupt();

    }

}

运行结果
...
Thread_end_demo ----  false -----  Mon May 18 10:41:46 CST 2019
Thread_end_demo ----  false -----  Mon May 18 10:41:46 CST 2019
Thread_end_demo ----  false -----  Mon May 18 10:41:46 CST 2019
Thread_end_demo ----  false -----  Mon May 18 10:41:46 CST 2019
Thread_end_demo ----  false -----  Mon May 18 10:41:46 CST 2019
set child thread interrupt atMon May 18 10:41:46 CST 2019
Thread_end_demo ----  false -----  Mon May 18 10:41:46 CST 2019

  可以看到在主线程调用子线程的interrupt()方法进行终止后,子线程成功终止。

  样例2:
  演示当主线程终止子线程时,子线程如果是休眠状态能否成功终止?

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class ThreadEndDemo2 implements Runnable {

    @Override
    public void run() {

        while (!Thread.currentThread().isInterrupted()) {

            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " throw InterruptedException : " + e + ";  at " + new Date());
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " ---------- " + Thread.currentThread().isInterrupted() + " ----  " + new Date());
        }

    }

    public static void main(String[] args) {

        Thread thread = new Thread(new ThreadEndDemo2(), "Thread_end_demo");

        thread.start();

        System.out.println("main thread start sleep 3s at " + new Date());
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main thread wakeup, set child thread interrupt at" + new Date());
        thread.interrupt();

    }

}

···运行结果
main thread start sleep 3s at Mon May 18 10:59:07 CST 2019
main thread wakeup, set child thread interrupt atMon May 18 10:59:10 CST 2019
Thread_end_demo throw InterruptedException : java.lang.InterruptedException: sleep interrupted;  at Mon May 18 10:59:10 CST 2019
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.bigshen.threads.end.ThreadEndDemo2.run(ThreadEndDemo2.java:14)
	at java.lang.Thread.run(Thread.java:745)
Thread_end_demo ---------- false ----  Mon May 18 10:59:10 CST 2019
Thread_end_demo ---------- false ----  Mon May 18 10:59:20 CST 2019
Thread_end_demo ---------- false ----  Mon May 18 10:59:30 CST 2019
Thread_end_demo ---------- false ----  Mon May 18 10:59:40 CST 2019
Thread_end_demo ---------- false ----  Mon May 18 10:59:50 CST 2019
Thread_end_demo ---------- false ----  Mon May 18 11:00:00 CST 2019
Thread_end_demo ---------- false ----  Mon May 18 11:00:10 CST 2019..
...

  可以看到,当线程Thread_end_demo是在休眠状态下时,如果主线程尝试将其终止,子线程会收到该终止指令并抛出 InterruptedException,但并不会真正将线程终止,而且子线程的isInterrupted()状态也被重置为了false。由此可以推断抛出的InterruptedException会触发线程状态的复位,即将interrupt状态重新改为false。这里其实从hotspot底层jvm源码中可以看出,不再继续展开。
  其实这里设计的意思已经很明显了:如果一个线程处于休眠状态时被终止,因为该线程可能在休眠后还有其余业务逻辑需要执行,这个需要交由子线程自己去判断。所以系统并不会直接终止该线程,而是会将该线程唤醒并抛出InterruptedException异常,交由子线程自己来判断是立刻终止、处理完一些业务后终止、还是不终止。
  样例3: 当线程处于休眠状态下时进行终止

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class ThreadEndDemo2 implements Runnable {

    @Override
    public void run() {

        while (!Thread.currentThread().isInterrupted()) {

            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                // 抛出InterruptedException异常,底层实现会触发线程 interrupt 状态复位
                System.out.println(Thread.currentThread().getName() + " -------- " + Thread.currentThread().isInterrupted() + "; throw InterruptedException : " + e + ";  at " + new Date());
//                e.printStackTrace();

                //TODO do something

                // 终止线程
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() + " ---------- " + Thread.currentThread().isInterrupted() + " ----  " + new Date());
        }

    }

    public static void main(String[] args) {

        Thread thread = new Thread(new ThreadEndDemo2(), "Thread_end_demo");

        thread.start();

        System.out.println("main thread start sleep 3s at " + new Date());
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main thread wakeup, set child thread interrupt at" + new Date());
        thread.interrupt();

    }

}

... 运行结果
main thread start sleep 3s at Mon May 18 11:19:53 CST 2019
main thread wakeup, set child thread interrupt atMon May 18 11:19:56 CST 2019
Thread_end_demo -------- false; throw InterruptedException : java.lang.InterruptedException: sleep interrupted;  at Mon May 18 11:19:56 CST 2019
Thread_end_demo ---------- true ----  Mon May 18 11:19:56 CST 2019

Process finished with exit code 0

  此例子在InterruptedException异常中将线程重新设置为终止状态。成功将线程终止。

posted @ 2019-07-06 19:28  BigShen  阅读(470)  评论(0编辑  收藏  举报