并发编程基础

线程

几个概念

  • 进程:程序的一次执行,是资源的基本分配单位
  • 线程:CPU调度的基本单元,同一个进程下的线程共享进程的资源,但每个线程都有自己的程序计数器和栈区域,线程间不共享

线程的创建方法

  • 实现Runnable接口:
  • 继承Thread,重写run方法
  • 使用FutureTask方式,构造参数可以接收Callable对象和Runnable对象

通知与等待

等待是线程处于阻塞状态,持有了锁,通知是唤醒等待的线程

wait方法

是Object的方法,对象将释放锁,如果对象没有获取到对象监视器时调用wait方法将抛出异常,因为只要线程持有了锁才能释放锁,获取对象监视器的方法可以使用Synchronized关键字,为了防止虚假唤醒(即没有使用notify唤醒线程)可以添加判断唤醒条件是否满足来处理。

共享对象调用wait方法,则当前线程只会释放当前共享对象的锁,其他的不会被释放。

wait方法还有个带超时参数的,如果在指定时间内被唤醒,那么将返回,继续往下走

notify方法

随机唤醒一个被挂起的线程,被唤醒的线程不一定执行,,需要获取到对象监视器才可以继续执行

notifyAll方法

唤醒所有因wait方法而被挂起的线程

等待线程终止

join方法

等待多个线程处理完任务后才返回,否则阻塞

线程休眠

sleep方法

该方法不释放锁,休眠不参与CPU的调度

让出CPU执行权

yield方法

让出CPU执行权,即使它还没有执行完让任务

线程中断

interrupt方法

中断线程,实际上只是设置有一个中断标志位的值为true,如果线程被挂起了,当有其他线程中断这个线程时会抛出InterruptedException异常。

isInterrupted方法

判断线程是否被中断

interrupted方法

该方法是静态的,注意这个与interrupt方法不同,这个方法作用是检查当前线程是否被中断,它与isInterrupted方法也不同:如果发现当前线程被中断了,则会清除中断标志。“获取的当前调用线程的中断标志而不是调用interruped方法的示实例对象的中断标志”,也就是如果主线程里的子线程调用了interrupt方法设置了中断标志位,那么执行interrupted方法获取的却是主线程的中断标志。

线程死锁

多个线程执行过程中,因争夺资源而造成相互等待的现象

死锁产生的条件:

  • 互斥条件
  • 请求和保持条件
  • 不可以剥夺条件
  • 环路等待条件

守护线程和用户线程

线程可以分为守护线程和用户线程,当用户线程还没有结束时,JVM不会退出, 而守护线程不会影响JVM的退出>。

ThreadLocal

提供保存线程本地变量的作用,对于ThreadLocal变量的操作,线程本地会有这个变量的本地副本,多个线程的操作都是操作自己本地内存里的副本。

每个线程Thread里都有一个ThreadLocalMap的对象threadLocals,key为ThreadLocal变量的this引用,value为自定义设置。

当设置value时,首先通过getMap获取到对应的线程变量ThreadLocalMap,如果不为空,则直接设置该值,否则创建并实例化ThreadLocalMap。

当获取value时,同样也是先获取ThreadLocalMap对象,通过获取ThreadLocalMap的内部类Entry来获取值。如果ThreadLocalMap对象为空,首先会进行初始化,初始化的value为null。

InheritableThreadLocal

该类继承了ThreadLocal,提供了子线程可以访问父线程设置的本地变量的作用。

并发有三个主要问题

  • 原子性
  • 有序性
  • 可见性

Synchronized关键字

提供原子性、有序性、可见性的保障,对被修饰的变量的获取途径由工作内存改为直接从主内存中获取。存在弊端是会引起线程上下文的切换。

作用范围

  • 代码块
  • 对象

volitile关键字

保证变量的可见性和禁止重排序,当修改变量的值时会立即将该值刷回主内存,但是不保证原子性

指令重排序

JMM会对不存在数据依赖性的指令进行重排序,已提交编译速度,重排序后的执行结果与程序顺序执行的结果一致

乐观锁和悲观锁

  • 悲观锁:认为数据很容易被其他线程修改,所以数据在处理前需要进行加锁
  • 乐观锁:认为数据一般情况下不会冲突,只有在数据提交更新时才会对数据冲突进行检测

公平锁和非公平锁

以线程获取锁的抢占机制可以将锁分为公平锁和非公平锁

  • 公平锁:线程获取锁的顺序以按照请求锁的时间顺序类决定

  • 非公平锁:与公平锁相比,不按照顺序来,获取锁的时机不一定,与公平锁性能开销比较,性能较高

    实现例子:ReentrantLock(isFairLock) 如果isFairLock为true则为公平锁,否则为非公平锁。

独占锁和共享锁

按照锁能被单个线程持有还是能被多个线程持有,可以将锁分为独占锁和共享锁

  • 独占锁:锁只能被一个线程持有,是一种悲观锁实现例子:ReentrantLock
  • 独占锁:锁能被多个线程持有,是一种乐观锁,实现例子:ReadWriteLock读写锁

可重入锁

持有锁的线程多次获取已经持有的锁的时候不被阻塞,那么该锁为可重入锁,synchronized内部锁是重入锁

重入N次,需要释放N次才能真正释放掉锁?

自旋锁

当前线程没有获取到锁时,并不立即阻塞,而是重试多次获取锁,如果尝试指定次数后还没有获取到锁,那么线程才会被阻塞。