Java并发系列 | 一文进入多线程的奥秘

写在前面: 多线程大家应该也不会陌生,同时也是面试的超级重点,掌握了多线程编程有利解决许多项目的并发性问题,提高自身硬实力。

本文目录

    • 并发编程简介
      • 影响服务器的吞吐量因素
      • 并行、并发
      • Java的线程
      • Thread使用场景
      • 线程的生命周期
      • Java线程的状态
      • 线程的启动
      • 线程的终止
        • 线程终止的拓展:
      • interrupt()的作用

并发编程简介

并发编程的本质是充分利用cpu资源。

影响服务器的吞吐量因素

硬件:CPU、磁盘、网络、内存

软件: 线程数量、JVM内存分配、网络通信机制(BIO、NIO、AIO)、磁盘IO

并行、并发

并行:同一时刻,多个线程同时执行。(多个CPU同时执行多个线程)

并发:同一时刻,多个线程交替执行。(一个CPU交替执行线程)

Java的线程

  • Thread 类
  • Runnable 接口
  • Callable/Future 带返回值

Thread使用场景

  • 网络请求分发的场景
  • 文件导入
  • 短信发送场景:(使用异步方式能够加快响应)

Java并发系列 | 一文进入多线程的奥秘_第1张图片

Java并发系列 | 一文进入多线程的奥秘_第2张图片

用户注册案例,异步线程调度:

@RestController
public class UserController {

    @Autowired
    IUserService userService;

    @Autowired
    SmsClient smsClient;

    @PostMapping("/user")
    public String addUser(User user) {
        long start = System.currentTimeMillis();
        userService.insert(user);
        long end = System.currentTimeMillis();
        return "SUCCESS:" + (end - start);
    }
    //创建一个线程池对象
    /**
     * 参数信息:
     * int corePoolSize     核心线程大小
     * int maximumPoolSize  线程池最大容量大小
     * long keepAliveTime   线程空闲时,线程存活的时间
     * TimeUnit unit        时间单位
     * BlockingQueue workQueue  任务队列。一个阻塞队列
     */

    @PostMapping("/sms/user")
    public String register(User user) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
        long start = System.currentTimeMillis();
        userService.insert(user);
        //异步.  Future ->
        //会创建N个线程
        //MQ来代替
        pool.submit(new Runnable() {
            @Override
            public void run() {
                smsClient.sendSms("xxxxx");
            }
        });
        long end = System.currentTimeMillis();
        pool.shutdown();
        return "SUCCESS:" + (end - start);
        
    }
}

线程的生命周期

Java并发系列 | 一文进入多线程的奥秘_第3张图片

Java线程的状态

Java线程的状态有6钟

    public enum State {
        // 创建
        NEW,
		// 运行时
        RUNNABLE,
        // 阻塞
        BLOCKED,
		// 等待,死死等待
        WAITING,
		// 超时等待
        TIMED_WAITING,
		// 销毁
        TERMINATED;
    }

Java并发系列 | 一文进入多线程的奥秘_第4张图片

需要注意的是,操作系统中的线程除去newterminated状态,一个线程真实存在的状态,只有:

  • ready:表示线程已经被创建,正在等待系统调度分配CPU使用权。
  • running :表示线程获得了CPU使用权,正在进行运算
  • waiting :表示线程等待(或者说挂起),让出CPU资源给其他线程使用

线程的启动

Java创建Thread类调用start方法,底层是把线程放到一个组里面,然后调用一个本地方法start0;方法底层是C++;Java无法操作硬件。

new Thread().start(); // 启动一个线程
Thread t1 = new Thread();
t1.run(); // 调用实例方法

如下代码:

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 */
        }
    }
}
//  调度本地方法	
    private native void start0();

Java调用本地方法start0后由JVM(底层c++)去执行相应的事情发起指令到系统去做线程调度,系统调度CPU,将线程启动信息返回给jvm,然后响应回给Java中。如下图:

Java并发系列 | 一文进入多线程的奥秘_第5张图片

线程的终止

线程会在run方法执行结束后终止

线程终止的拓展:

  1. 使用stop()方法终止线程。
public class TestDemo extends Thread{
    @Override
    public void run() {
       //线程会执行的指令
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Come in");
    }

    public static void main(String[] args) {
        TestDemo testDemo = new TestDemo();
        testDemo.start();
        testDemo.stop(); //不建议 强制终止这个线程。
        //发送终止通知
    }
}
  1. 使用interrupt()告知线程终止
public class InterruptDemo02 implements Runnable {
    @Override
    public void run() {
        // 判断线程是否已经被中断
        while (!Thread.currentThread().isInterrupted()) { //false
            try {
                TimeUnit.SECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //可以不做处理,
                //继续中断 ->
                Thread.currentThread().interrupt(); //再次中断
                //抛出异常。。
            }
        }
        System.out.println("processor End");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new InterruptDemo02());
        t1.start();
        Thread.sleep(1000);
        t1.interrupt(); // 中断线程
        //Thread.interrupted() ;//复位
    }
}

总结:

  1. 我们不推荐第一种方法做线程终止,因为线程有可能还在执行着逻辑,这样会使得程序数据存在丢失
  2. 使用Thread.currentThread().isInterrupted()的状态来对线程做操作
  3. 如果一个线程处于了阻塞状态,我们可以对线程进行唤醒操作,其中sleep()join()wait()方法都会抛出InterruptedException的异常

Java并发系列 | 一文进入多线程的奥秘_第6张图片

c++对线程状态设置源码:

void os::interrupt(Thread*thread){
    assert(Thread::current()==thread||Threads_lock->owned_by_self(),
           "possibility of dangling Thread pointer");
    
    OSThread*osthread=thread->osthread();
    if(!osthread->interrupted()){
        osthread->set_interrupted(true); //设置一个中断状态
        // More than one thread can get here with the same value of osthread,
        // resulting in multiple notifications. We do, however, want the store
        // to interrupted() to be visible to other threads before we execute
        unpark().
            OrderAccess::fence();
        ParkEvent*const slp=thread->_SleepEvent; //如果是sleep中,唤醒
        if(slp!=NULL)slp->unpark();
    }
    // For JSR166. Unpark even if interrupt status already was set
    if(thread->is_Java_thread())
        ((JavaThread*)thread)->parker()->unpark();
    ParkEvent*ev=thread->_ParkEvent;
    if(ev!=NULL)ev->unpark();
}

interrupt()的作用

  • 设置一个共享变量的值true
  • 唤醒处于注册状态下的线程

以下文章补充:interrupt的知识、interrupt方法

你可能感兴趣的:(Java并发编程,java,多线程,高并发,JUC,Thread)