【无标题】

Java多线程并发

  1. Java线程实现/创建方式
    线程实现方式包括继承Thread类与实现Runnable接口
    线程启动的唯一方式是Thread::start,start()是一个本地方法,执行之后将启动一个新线程并等待CPU调度运行run()方法
    如果涉及有返回值的线程方法,则需要实现callable接口,并以ExecutorService方式调用
  2. 4种线程池
    Java线程池顶级接口为Executor,仅包含execute(Runnable command)一个方法,真正实现线程池功能的为ExecutorService接口。线程池主要实现类有:
ThreadPoolExecutor:

创建出与核心线程数一致的线程数量,执行与核心线程数对应的任务数量,如果还有未执行的任务并且当前等待队列未满,加入等待队列,如果等待队列满,且还有任务提交进来,判断当前线程数是否等于池内最大线程数,不等于且小于的话,开启临时线程执行任务,若最大线程数等于池内线程数且等待队列已满,且还有任务继续提交进来,执行执行的拒绝策略reject handle,核心线程执行完任务后调用take()方法继续接任务执行或者阻塞等待keep alive,存活在线程池中等待下一次任务,达到线程复用,非核心线程执行完任务后假如没有任务执行了,且超出了预定的过期时间后,执行poll()方法销毁;

int corePoolSize:初始线程数

int maximumPoolSize:最大线程数

long keepAliveTime:线程存活时间

TimeUnit unit:线程存活时间单位

BlockingQueue workQueue:阻塞队列

ArrayBlockingQueue:数组阻塞队列,先进先出原则

LinkedBlockingQueue:链表阻塞队列,先进先出,吞吐量高于数组阻塞队列

SynchronousQueue:不存储元素的无界阻塞队列,任务插入操作等待,有空闲的线程调用或者创建新线程时候移除操作,否则插入操作一直阻塞,吞吐量高于链表阻塞队列,不属于一种容器队列,只是类似一种任务分发

PriorityBlockingQueue:优先无界阻塞队列,按照优先级对元素进行排序

ThreadFactory threadFactory:创建线程

RejectedExecutionHandler handler:饱和拒绝策略

(1)AbortPolicy(默认)直接拒绝

(2)CallerRunsPolicy:使用调用者的线程执行任务,不抛出异常不抛弃任务,将任务返还给调用主线程去执行,趁这段时间处理线程池正在执行的任务

(3)DiscardOldestPolicy:放弃队列中等待最久的任务(注意不能跟优先级队列组合使用,不然会把优先级最高的最老的任务抛弃)

(4)DiscardPolicy:抛弃当前任务
/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
ScheduledThreadPoolExecutor:

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

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

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

newScheduledThreadPool:定时调度线程池

newSingleThreadExecutor:单线程池
  1. 线程生命周期与终止线程的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()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

    线程终止方式:

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

    线程sleep过程中如果被打断,会抛出异常 InterruptedException,sleep会提前结束,并且此时线程的打断标志会被清除。如果需要在被打断时做出响应,可以捕获异常后处理。

  2. interrupt与synchronized

    interrupt无法干扰正在阻塞等待 synchronized 锁的线程

  3. interrupt与lock

    interrupt无法打断等待lock锁的线程,如果需要线程等待锁的过程中可以被打断,使用 lockInterruptibily

  4. 小结

    interrupt可以通过设置线程的中断状态来达到通知的目的,具体是否需要做出响应可以视情况而定。比如想在线程外部终止线程的执行而又不破坏数据一致性,比如多线程争抢锁并操作数据的情况,可以利用interrupt来通知线程,线程内部通过中断状态来优雅的终止,而不是强制杀死线程。

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

     1. stop方法终止:废弃,会发生不可预知的异常情况
    
  5. sleep,wait,start,run,join
  6. Java锁机制
    1. CAS

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

    1. AQS

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

  7. Java锁类型
    1. 乐观锁

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

    1. 悲观锁

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

    1. 自旋锁

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

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

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

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

    1. Synchronized同步锁

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

    Synchronized作用范围:

     	- 作用于方法上,则将当前对象this加锁,访问当前对象的同步方法时会阻塞
    
作用于静态方法上,则将Class对象加锁,由于Class对象存储于方法区,属于全局共享对象,因此静态方法锁相当于一个类的一个全局锁,对于此类的所有对象调用静态同步方法的线程都会加锁。
package eric.study.jvm.thread;

import java.util.concurrent.TimeUnit;

/**
 * 1.加锁后只对后序其他带锁的代码或方法有影响,非同步代码不影响
 * 2.静态方法同步后,由于Class实例为全局共享,所以此类的所有对象的同步代码都一并竞争同一个锁资源
 * 3.静态代码块锁住的是Class实例,不影响当前类的实例,即静态代码同步锁不影响非静态代码同步,只是对于所有对象调用静态同步代码会阻塞。
 * @since 2022-04-21
 */
public class SynchronizedStaticTest {

    public static void main(String[] args) {
        System.out.println("main start");
        SynchronizedStaticThread test1 = new SynchronizedStaticThread();
        SynchronizedStaticThread test2 = new SynchronizedStaticThread();
        Thread t1 = new Thread(test1, "A");
        Thread t2 = new Thread(test2, "B");
        t1.start();
        t2.start();
        System.out.println("main end");
    }


    public static class SynchronizedStaticThread implements Runnable {
        public synchronized static void syncStaticMethod() {
            System.out.println(Thread.currentThread().getName() + " syncStaticMethod start");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " syncStaticMethod end");
        }

        public synchronized void syncMethod() {
            System.out.println(Thread.currentThread().getName() + " syncMethod start");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " syncMethod end");
        }

        @Override
        public void run() {
            syncStaticMethod();
//            syncMethod();
        }
    }
}

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

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

1. ReetrantLock

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


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

```other
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(); // 解锁,需要手动操作否则导致死锁
}
```

1. Samaphore信号量
2. AtomicInteger

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


1. volatile关键字
	2. 保证线程可见性

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

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


	1. 不保证原子性

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


	1. 禁止指令重排序

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


			1. 申请内存空间
			2. 初始化变量
			3. 变量关联

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

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

注:


		1. 双重校验的线程同步单例模式变量必须使用volatile修饰
		2. 当线程调用了synchronized修饰的方法时,会主动从主内存刷新变量值
  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值