【Java并发编程】并发基础 —— 线程

时间:2020-12-09 01:09:49

一、线程

线程是操作系统调度的最小单元,多线程同时执行,可以提高程序性能 。

1. 什么是线程

操作系统运行一个程序,就会创建一个进程,在一个进程里可以创建多个线程,因此线程也叫做轻量级进程 。

2. 线程带来了什么好处

现代处理器都是多核的,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行在一个处理器核心上,如果一个单线程程序在运行时只能使用一个处理器核心,那么再多的处理器核心加入也无法显著提升该程序的执行效率。

3. 线程基础

(1)优先级 —— setPriority(int)

线程优先级决定线程需要多或者少分配一些处理器资源的线程属性

设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

程序正确性不能依赖于优先级高低 —— 不同操作系统有不同的处理方式

(2)状态 ——

New 初始态\ Running 运行态 \ Blocked 阻塞态 \Waiting 等待态\ TimeWaiting 超时等待态\ Terminated 终止态

注: Java将操作系统中的运行和就绪两个状态合并称为运行状态

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

(3)Daemon 线程 —— setDaemon(true)

支持型线程,用作程序中台调度以及支持性工作
当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出

(4)线程的启动和终止

新构造的线程对象是由其parent线程来进行空间分配的,child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程,至此,一个能够运行的线程对象就初始化好了。

start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程

(5)中断 Thread.currentThread().isInterrupted()

  • 中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作;
  • 中断状态是线程的一个标识位,中断操作是一种简便的线程间交互方式,这种交互方式适合用来取消或停止任务
  • 除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程

4. volatile和synchronized关键字

volatile

告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

synchronized

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

  • 本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器 ——
    任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

5. 等待通知

等待/通知机制是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上。

  • 使用wait()、notify()和notifyAll()时需要先对调用对象加锁;
  • 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列;
  • notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回;
  • notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED;
  • 从wait()方法返回的前提是获得了调用对象的锁。
synchronized(对象) {
       while(条件不满足) {
            对象.wait();
       }
       对应的处理逻辑
}
synchronized(对象) {
       改变条件
       对象.notifyAll();
}

(1)Thread.join()

线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回

join() 当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。

(2)ThreadLocal

以ThreadLocal对象为键、任意对象为值的存储结构,一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。

ThreadLocal 调用耗时统计的功能上,在方法的入口前执行begin()方法,在方法调用后执行end()方法,好处是两个方法的调用不用在一个方法或者类中,比如在AOP(面向方面编程)中,可以在方法调用前的切入点执行begin()方法,而在方法调用后的切入点执行end()方法,这样依旧可以获得方法的执行耗时。

6. 如何实现等待超时模式

场景:调用一个方法时等待一段时间0,如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。
解决:对象加锁 条件循环 处理逻辑 + 设置超时时间段
应用:超时等待模式可以用来实现 数据库连接池 中数据库连接操作超时返回
好处: 保证客户端线程不会一直挂在连接获取的操作上,而是“按时”返回,并告知客户端连接获取出现问题,是系统的一种自我保护机制,针对昂贵资源(比如数据库连接)的获取都应该加以超时限制。

7. 线程池

为什么需要考虑 C/S 架构的程序,如果没有线程池, 我们客户端向服务器发的请求,每个请求创建一个线程,假设我们有 1 万个请求, 那么就需要创建 1 万的线程进行处理。 众所周知,线程的创建和上下文切换是非常消耗资源的。因此, 需要线程池技术解决这个问题 。
线程池好处

  • 消除了频繁创建和消亡线程的系统资源开销
  • 面对过量任务的提交能够平缓的劣化
    线程池的默认实现
    使用一个线程安全的工作队列连接工作者线程和客户端线程。
    线程池应用: 简单的 Web 服务器
    常用的Java Web服务器,如Tomcat、Jetty,在其处理请求的过程中都使用到了线程池技术。 因为我们不能一个一个请求的顺序处理,这样的话,估计就没有用户会想用了 。现代的浏览器可以并发发送多个请求, 服务器可以并发处理多个请求 。
    线程池中线程数量设置线程池中线程数量并不是越多越好,具体的数量需要评估每个任务的处理时间,以及当前计算机的处理器能力和数量。使用的线程过少,无法发挥处理器的性能;使用的线程过多,将会增加系统的无故开销,起到相反的作用。