Java多线程并发

Java线程实现/创建方式

线程实现方式包括继承Thread类与实现Runnable接口

线程启动的唯一方式是Thread::start,start()是一个本地方法,执行之后将启动一个新线程并等待CPU调度运行run()方法

如果涉及有返回值的线程方法,则需要实现callable接口,并以ExecutorService方式调用

4种线程池

Java线程池顶级接口为Executor,仅包含execute(Runnable command)一个方法,真正实现线程池功能的为ExecutorService接口。线程池主要实现类有:

ThreadPoolExecutor:

ScheduledThreadPoolExecutor:

线程池工具类Executors提供构造4中具体含义的线程池:

newCachedThreadPool:提供一个带60s缓冲的线程池,设定初始线程数与最大线程数

newFixedThreadPool:固定大小线程池,线程执行结束如果无新任务立即销毁

newScheduledThreadPool:定时调度线程池

newSingleThreadExecutor:单线程池

线程生命周期与终止线程的4种方式

新建:使用new关键字创建线程之后,处于新建状态JVM分配内存并初始化属性

就绪:调用start()方法之后,JVM为其创建虚拟机栈和程序计数器并等待CPU调度

运行:获得CPU调度,执行run()方法体

阻塞:

等待阻塞(o.wait->等待对列):运行的线程执行 o.wait()方法,JVM 把该线程放入等待队列中

同步阻塞(lock->锁池):运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。

其他阻塞(sleep/join):运行的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。

死亡:

正常结束:run()或 call()方法执行完成,线程正常结束。

异常结束:线程抛出一个未捕获的 Exception 或 Error。

调用 stop:直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

线程终止方式:

正常运行结束:线程运行run方法完毕,或设置终止状态,在线程运行过程中更改状态完成循环跳出

interrupt方法终止:

调用线程的interrupt方法,会设置线程的终止状态为true,并产生InterruptedException异常,可以通过isInterrupt()方法能取得终止状态。但设置interrupt后在catch中捕获异常,并不会直接终止线程,需要在异常补货之后使用break终止循环状态;另外,使用ExecutorService启动的线程对interrupt不生效

stop方法终止:废弃,会发生不可预知的异常情况

sleep,wait,start,run,join

Java锁机制

CAS

Compare and Swap,更改前先与原来的值比对,如果不一致则更新失败。多应用于Unsafe类中,采用自旋锁+CAS机制更新

AQS

AbstractQueuedSynchronizer,一个抽象类,定义了一套多线程下访问共享资源的方案。内部主要包含一个volatile int state的属性标记当前状态,和一个FIFO的阻塞队列用来存储等待资源的线程。state状态更新是采用自旋锁+CAS机制。AQS中设置了3中共享方式:独享,共享,独享+共享,主要取决于各个实现类内部的实现方案。主要实现类有:ReentrantLock(独享),CountDownLatch(共享),ReentrantReadWriteLock(独享+共享)

Java锁类型

乐观锁

读多写少,读取的时候不加锁,写入是通过CAS机制比较版本,加锁写入。

悲观锁

悲观锁在写多读少的场景下,任务每次读写前其他线程都可能更新了数据所以要加锁读写,典型如synchronized。

自旋锁

如果持有锁的线程可能在很短的时间内释放锁,则等待竞争锁的线程就不必进入阻塞状态,而是自旋等待锁释放后获取锁,避免线程状态切换的损耗。

优点:合适场景下,避免线程切换上下文带来的消耗

缺点:如果线程持锁时间长,或锁竞争激烈则CPU消耗更大。

自适应自旋锁:JDK6开始JVM对于自旋执行时间根据状态判断确定,而非之前的固定值。

Synchronized同步锁

Synchronized关键字将任意一个非null对象当作锁。属于独占式悲观锁,也属于可重入锁,非公平锁

Synchronized作用范围:

作用于方法上,则将当前对象this加锁,访问当前对象的同步方法时会阻塞

作用于静态方法上,则将Class对象加锁,由于Class对象存储于方法区,属于全局共享对象,因此静态方法锁相当于一个类的一个全局锁,对于此类的所有对象调用静态同步方法的线程都会加锁。

作用于属性对象,锁住的是以此特殊对象为锁的代码块。

Synchronized实现:

ContentionList:竞争队列,一个非具体的队列存放竞争线程,从头部放入尾部取出

EntryList:为了增加性能,将有资格竞争锁资源的一部分线程从CotentionList中取出

WaitSet:阻塞线程存放集合

OnDeck:当前竞争锁资源的线程,每次只有一个线程可以竞争资源

Owner:当前持有锁的线程

!Owner:释放锁的线程

当一个线程开始竞争当前锁资源时,先自旋尝试获取锁资源(非公平性),如果超过自旋时间阀值还未获取到资源,则从头部插入ContentionList;由于ContetionList中太多线程不断通过CAS并发访问会导致资源消耗,因此JVM会将一部分线程转移至EntryList中;Owner线程Unlock时,JVM从ContentionList尾部将一部分线程转移至EntryList,并指定某一个线程为OnDeck(一般是最先进来的),OnDeck线程并不直接获得锁,而是获得竞争权(与自旋的线程一并竞争),虽然失去公平性,但是增加了吞吐量;OnDeck线程获得锁资源后变成Owner,如果出发wait后阻塞进入WaitSet,直到notify再次唤醒会重新进入EntryList,ContentionList,EntryList,WaitSet中的线程都属于阻塞线程;

锁升级:从偏向锁升级到轻量锁再到重量锁,这也是JDK8开始对synchronized的优化

Image.png

ReetrantLock

ReetrantLoc是一种可重入锁,与Synchronized功能基本相同,只不过

额外提供了诸如可响应中断锁、可轮询锁请求、定时锁,指定唤醒等功能,避免多线程死锁的方法。

需要手动加锁与解锁

private final Lock lock = new ReetrantLock(false);// 默认非公平锁,可设置为true,即公平锁

private final Condition condition = lock.newCondition();

try{

lock.lock();// 锁定

...

condition.await();// 等待,等同于Object中的wait()方法

...

condition.signal();// 唤醒,等同于Object中的notify(),ReentrantLock可以指定唤醒条件,Object只能随机唤醒

condition.sinalAll()// 唤醒所有等待线程,等同于Object中的notifyAll()

}finally{

lock.unlock(); // 解锁,需要手动操作否则导致死锁

}

Samaphore信号量

AtomicInteger

自旋锁+CAS机制,属于乐观锁,独占锁

volatile关键字

保证线程可见性

多线程读取volatile修饰的变量时不再加载到线程缓冲区,而是直接读取主内存的变量值

多线程写入volatile修饰的变量时,会主动通知其他线程刷新

不保证原子性

volatile修饰的变量不能保证原子性,例如i++在编译后的字节码指令分为多个步骤,volatile不能保证这一组指令运行不会被打断,因此多个线程同时计算i++时通常不能得到预期结果。java使用锁机制来保证原子性,如synchronized或lock。

禁止指令重排序

jvm在编译和运行期会优化字节码指令,对于不影响运行结果的字节码顺序可能有变化,例如实例化对象时分为:

申请内存空间

初始化变量

变量关联

但步骤2和3没有关联性因此可能会被重排序为1,3,2这时当外部线程判断对象是否为空时返回false,然而此时的对象还未创建完成导致无法使用。

如果使用volatile关键字修饰后,虚拟机会保证上述步骤按顺序完成这时获取到的对象才能保证可用。

注:

双重校验的线程同步单例模式变量必须使用volatile修饰

当线程调用了synchronized修饰的方法时,会主动从主内存刷新变量值

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值