红泥小火炉


并发编程

Nathaniel 2021-12-13 83浏览 0条评论
首页/正文
分享到: / / / /

基本概念

同步&异步

同步:调用方需要等待结果响应返回;

异步:调用方不需要等待结果响应返回,处理完毕之后数据会通过另外的接口返回。

并发&并行

并发:多个任务交替执行,访问的往往是同一资源;

并行:多个任务同时平行执行,处理的往往不是同一资源。

临界区

标识公共资源或共享数据,可以被多个线程使用。

阻塞&非阻塞

形容多线程之间的相互影响。

阻塞:当前线程访问资源,需要等待其他线程释放资源占用;

非阻塞:当前线程访问资源,不受其他线程的阻碍和影响。

死锁&饥饿&活锁

死锁:多个线程互相占有对方需要的资源,资源无法释放,多个线程都无法继续。

饥饿:线程因为种种原因无法获得所需要的资源,导致一直无法执行。

活锁:资源在多个线程之间转换持有,但所有的线程均无法获取到所需要的全部资源。

线程&进程

进程:是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

线程:是轻量级的进程,是程序执行的最小单位。

线程

线程状态

public enum State{
    NEW, // 线程刚创建,还未开始执行
    RUNNABLE, // 线程在执行中
    BLOCKED, // 执行中遇到synchronized同步块,会被阻塞
    WAITING, // 线程进入了无时间限制的等待状态
    TIMED_WAITING, // 线程处于一个有时限的等待
    TERMINATED // 线程执行完毕,结束的状态
}

线程的基本操作

新建

Thread thread = new Thread();

可以继承Thread类,也可以实现Runnable接口,然后使用Thread类承载Runnable接口。

终止

  • stop():暴力停止,强行终止当前运行的线程,立即释放当前线程所持有的锁,如果这些锁是用来维护对象一致性的话,可能会引起数据不一致的问题,不建议使用。

  • 自定义线程终止标识,由程序员来决定线程何时终止。

中断

线程协作机制。触发线程中断并不会使当前线程立即退出,而是给线程发送一个通知,告知目标线程,至于目标线程接到通知之后如何处理,完全由目标线程自行决定。

public void interrupt() // 中断线程,通知目标线程中断,设置中断标志位,线程并不会中断
public void isInterrupted() // 判断是否被中断,可以用于终止时的判断处理
public static boolean interrupted() // 判断是否被中断,并清除当前中断状态

休眠

如果sleep方法由于中断而抛出异常,此时会清除中断标记。

public static native void sleep(long millis) // 会让当前线程休眠若干时间

等待&通知

对象实例调用wait()之后,当前线程会在这个对象上等待。

线程调用wait()之后,当前线程会进入该对象的等待队列。对象实例的wait()和notify()必须包含在synchronized语句中(必须获取到当前对象的监视器锁),且wait()方法会释放目标对象的锁。

public final void wait() throws InterruptedException;
public final native void notify(); // 从目标对象的等待队列总随机唤醒一个线程,而notifyAll()则可以唤醒所有的线程。 

挂起&继续执行

被挂起的线程必须等到resume()之后,才可以继续执行。

这两个方法并不推荐使用,suspend()执行之后会导致线程持有的锁不会被释放,而且被挂起的线程状态是Runnable。

public final void suspend(); // 挂起
public final void resume(); // 继续执行

等待线程结束(join) 和退让(yeild)

public fianl void join() throws InterruptedException; // 无限等待,阻塞当前线程,直到目标线程执行完毕
public final synchronized void join(long millis) throws InterruptedException; // 超过给定时间目标线程还在执行,当前线程不再等待,继续执行
public static native void yield(); // 会使当前线程让出CPU占用

关键字

volatile:可以保证目标对象的可见性和有序性,但是无法保证复合操作的原子性。
synchronized:实现线程间的同步,对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证了安全性,也可以保证有序性和可见性。

线程组

ThreadGroup threadGroup = new ThreadGroup();

线程组中会存放相同功能的线程。

activeCount()会统计出当前线程组内的活动线程数量;

list()会列出当前线程组内的所有线程信息。

守护线程

t.setDaemon(true); // 设置t线程为守护线程

守护线程的设置必须在start()之前进行,否则会被当作是普通的用户进程。

线程优先级

线程优先级使用1-10来标识,高优先级的线程倾向于更快执行完成。

t.setPriority();

同步控制

重入锁

可以完全替代synchronized,使用java.util.concurrent.locks.ReentrantLock类实现。

重入锁是可以重复进入的,也就是说一个线程可以多次获取同一把锁,只不过获取多少次锁,最后就要释放多少次锁。

ReentrantLock lock = new ReentrantLock();
lock.lock(); // 加锁
lock.unlock(); // 释放锁

重入锁可以进行中断响应。如果一个线程正在等待锁,那么会收到一个通知,无需继续等待。

lock.lockInterruptibly(); // 可以对中断进行响应的锁申请,在等待锁的过程中,可以响应中断。 

重入锁可以实现限时等待,在等待指定时间之后,如果还是没有获取到锁,就退出锁等待。

lock.tryLock(); // 可以指定等待的时间和单位,也可以不指定。

重入锁可以实现锁公平,即按照先后顺序,先到先得,不会产生饥饿,但是公平锁实现成本较高,性能较低。

ReentrantLock fairLock = new ReentrantLock(true);

Condition对象

与重入锁配合使用,可以让线程在合适的时间等待,或者在指定的时间继续执行。

Condition condition = lock.newCondition();
condition.await(); // 线程在condition对象上等待
condition.signal(); // 唤醒等待在condition对象上的线程

信号量

可以指定多个线程,同时访问某一个资源。

public Semaphore(int permits); // permits 为准入数,即同时允许多少线程访问资源
public Samaphore(int permits,boolean fair);
public void acquire(); // 尝试获取准入,没有获得时等待,直到有准入释放或者当前线程被中断
public void acquireUninterruptibly();
public boolean tryAcquire(); // 尝试获取准入,失败则不会等待
public boolean tryAcquire(Long timeout,TimeUnit unit);
public void release(); // 释放准入许可

读写锁

有效减少锁竞争,提升系统性能

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock(); //获取到读锁
lock.writeLock(); // 获取到写锁

读-读不互斥,读-写、写-写阻塞。

CountDownLatch

用来控制线程等待,可以让线程等待直到倒计数结束,再开始执行。

CountDownLatch c = new CountDownLatch(10); // 10标识计数个数
c.countDown(); // 用于计数操作

线程池

目的:让线程复用;

ThreadPoolExecutor表示一个线程池;其中的主要参数说明如下:

workQueue:提交但未执行的任务队列,是一个BlockingQueue接口对象,只存放Runnbale对象
构造时可以使用已下队列:
* 直接提交队列:SynchronousQueue,提交任务不会被保存,总是创建新任务提交给线程执行,无空闲时,尝试创建新的进程。
* 有界任务队列:ArrayBlockingQueue:实际线程数<corePoolSize时,优先创建新线程,>corePoolSize时,新任务加队列
* 无界任务队列:LinkedBlockingQueue:实际线程数<corePoolSize时,优先创建新线程,达到corePoolSize之后,新任务加队列
* 优先任务队列:PriorityBlockingQueue:同无界队列,根据任务优先级顺序来执行

调度逻辑如下:

1.任务提交,判断是否实际线程数是否小于corePoolSize;
2.实际线程数小于corePoolSize,则分配线程执行;
3.实际线程数大于corePoolSize,提交队列等待,判断是否提交成功;
4.成功提交等待队列,则等待执行;
5.提交等待队列失败,则提交线程池,判断线程数是否达到max线程数;
6.达到max线程数,则提交失败,拒绝执行;
7.没有达到max线程数,则分配线程执行。
handler:拒绝策略。
* AbortPolicy:直接抛出异常;
* CallerRunsPolicy:在线程池未关闭时,直接在调用者线程中运行当前被丢弃的任务。
* DiscardOldestPolicy:丢弃最老的一个请求。
* DiscardPolicy:默默丢弃无法处理的任务。
* 自定义策略,扩展RejectedExecutionHandler接口即可。

创建线程池

newFixedThreadPool(); // 创建固定线程数量的线程池,任务占满之后,会等待在队列中
newSingleThreadExecutor(); // 创建一个容量为1的线程池,任务占满之后会等待在队列中
newCacheThreadPool(); // 创建一个可根据实际情况调整线程数量的线程池,占满之后,新任务会创建新的线程来处理
newSingleThreadScheduledExecutor(); // 创建一个大小为1,可以在给定时间执行某任务的线程池
newScheduledThreadPool(); // 创建一个可以指定线程数量的线程池

newScheduledThreadPool会返回一个ScheduledExecutorService对象,其内部方法:

schedule(); // 在指定时间,对任务进行一次调度。
scheduleAtFixedRate(); // 以上次任务开始执行时间为起点,在指定的间隔时间调度下一次任务,固定的频率。
sechduleWithFixDelay(); // 上次任务执行完毕之后,会按照给定的延时来执行下一个任务

线程池监控

线程池提供了beforeExecute()、afterExecute()、terminated()三个接口用于对线程池执行前、执行后、结束时的监控。

@Override
protected void beforeExecute(Thread t,Runnable r){}
@Override
protected void afterExecute(Runnable r,Throwable t){}
@Override
protected void terminated(){}

建议线程数设置

线程数=cpu数量*目标CPU的使用率*(1+等待时间与计算时间的比率)

在生产环境上使用execute来代替submit提交任务。

并发容器

(后续会再详细说明,敬请期待)

使map线程安全的方式

  • 使用Collections.synchronizedMap;会生成SynchronizedMap来确保传入map的安全性;内部维护了一个mutex锁来实现对传入的map进行操作的互斥。
  • 使用ConcurrentHashMap

使List线程安全的方式

  • 使用Vector;
  • 使用Collections.synchronizedList();
  • 使用CopyOnWriteArrayList来替换arrayList;使用ConcurrentLinkedQueue替换LinkedList;

锁优化建议

  • 减少锁持有时间:只在需要同步的地方加锁,这样有助于降低锁冲突的可能性,进而提升系统的并发能力。
  • 减小锁粒度:减小锁定对象的范围,降低锁冲突的可能性,提升系统的并发能力。
  • 用读写分离锁来替换独占锁:适用于读多写少的场景;
  • 锁分离:将锁按照不同的功能和类别进行分离,避免锁竞争;
  • 锁粗化:将一连串的锁操作整合成对锁的一次请求,减少加锁的次数;

JDK内部的锁优化

  • 锁偏向:如果线程获得了锁,那么锁就会进入偏向状态,当这个线程再次请求锁时,无需再同步;适合没有锁竞争的场合,不适合锁竞争激烈的场景。-XX:+UseBiasedLocking开启;
  • 轻量级锁:将对象头部作为指针指向持有锁的线程堆栈内部,以此判断线程是否持有对象锁。失败之后会转为重量级锁。
  • 自旋锁:当前线程空循环执行,等待锁,若干循环之后,如果还是无法获取到锁,则将线程挂起。
  • 锁消除:在JIT编译阶段,去除不可能存在共享资源竞争的锁。
最后修改:2021-12-13 22:22:30 © 著作权归作者所有
上一篇

评论列表

还没有人评论哦~赶快抢占沙发吧~

博客信息

  • 文章数目 15
  • 标签数目 9
  • 运行天数
  • 最后活动

广告

文章目录