博客地址(点击即可访问) | github源码地址 |
---|---|
深刻理解JMM(JAVA内存模型) | https://github.com/zz1044063894/JMMprojcet |
volatile详解 | https://github.com/zz1044063894/volatile |
线程基础,java并发程序基础 | https://github.com/zz1044063894/thread-base |
线程进阶,JDK并发包 | https://github.com/zz1044063894/JDK-concurrency |
多线程进阶,性能优化之锁优化 | https://github.com/zz1044063894/lock-optimization |
线程进阶,性能优化之无锁 | https://github.com/zz1044063894/no-lock# 多线程的团队合作 |
同步控制是并发程序必不可少的。前面我们已经介绍了synchronized关键字,它决定了一个线程是否可以访问临界区资源。同事obj.wait()和obj.notify()方法起到了重要的作用。接下来我们介绍他们的替代品。
重入锁完全可以代替synchronized关键字,在JDK5.0的再去版本中,重入锁的性能远远高于synchronized,但是从JDK6.0开始,JDK在synchronized做了大量的优化,使得两者的性能差距不大
在之前的博客中,我们做了一个10个线程进行10000次i++的操作,接下来我们使用重入锁做同样的操作
package com.jingchu.juc.relock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @description: 重入锁测试
* @author: JingChu
* @createtime :2020-07-21 10:07:39
**/
public class MyRELock implements Runnable {
private static int i = 0;
public static ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
for (int k = 0; k < 10000; k++) {
reentrantLock.lock();
try{
i++;
}finally {
reentrantLock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int j = 0; j < 10; j++) {
threads[j] = new Thread(new MyRELock());
threads[j].start();
}
for (int j = 0; j < 10; j++) {
threads[j].join();
}
System.out.println(i);
}
}
上述代码使用重入锁保护临界资源i,确保多线程对i的安全性,从以上代码可以看出,与synchronized相比lock有着明显的操作过程。开发人员必须手动指定何时加锁,何时释放锁。也正是这个原因,重入锁比synchronized更加灵活。注意:在退出临界区时,记得释放锁,否则其他线程无法获得锁,无法访问临界区
至于重入锁这个名字的由来,是因为他支持在一个线程内反复加同一个锁。上面的加锁过程可以改写成如下代码
reentrantLock.lock();
reentrantLock.lock();
try{
i++;
}finally {
reentrantLock.unlock();
reentrantLock.unlock();
}
除了上述功能外,重入锁还提供了了其他功能
package com.jingchu.juc.relock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @description: 死锁的锁中断解决
* @author: JingChu
* @createtime :2020-07-21 10:28:25
**/
public class LockInterrupt implements Runnable {
public static ReentrantLock reentrantLock1 = new ReentrantLock();
public static ReentrantLock reentrantLock2 = new ReentrantLock();
int lock;
/**
* 控制加锁顺序,方便构成死锁代码
*
* @param lock
*/
public LockInterrupt(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
reentrantLock1.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock2.lockInterruptibly();
}else {
reentrantLock2.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (reentrantLock1.isHeldByCurrentThread()) {
reentrantLock1.unlock();
}
if (reentrantLock2.isHeldByCurrentThread()) {
reentrantLock2.unlock();
}
System.out.println(Thread.currentThread().getId()+"退出线程");
}
}
public static void main(String[] args) throws InterruptedException {
LockInterrupt lockInterrupt1 = new LockInterrupt(1);
LockInterrupt lockInterrupt2 = new LockInterrupt(2);
Thread t1 = new Thread(lockInterrupt1);
Thread t2 = new Thread(lockInterrupt2);
t1.start();t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
可以看出中断后两个线程都退出了,但是真正完成工作的只有t1,而t2线程则放弃了起任务直接退出,释放资源。
package com.jingchu.juc.relock;
import org.omg.PortableServer.THREAD_POLICY_ID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @description: 锁限时等待
* @author: JingChu
* @createtime :2020-07-21 10:51:14
**/
public class LockWaitTime implements Runnable{
public static ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
try {
if(reentrantLock.tryLock(3, TimeUnit.SECONDS)){
Thread.sleep(4000);
}else {
System.out.println("获取锁失败");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if(reentrantLock.isHeldByCurrentThread()){
reentrantLock.unlock();
}
System.out.println(Thread.currentThread().getId()+"线程运行结束");
}
}
public static void main(String[] args) {
LockWaitTime lockWaitTime = new LockWaitTime();
Thread t1 = new Thread(lockWaitTime);
Thread t2 = new Thread(lockWaitTime);
t1.start();
t2.start();
}
}
在上面的例子中,由于占用锁的线程会持续4秒,获取锁的线程3秒内获取不到锁,因此会获取锁失败。
tryLock()方法可以不带参数,这时,线程会尝试获取锁,如果锁没有被其他线程占用,则申请锁成功,并返回true,如果锁被占用,不会继续等待,而是立即返回false,这种模式不会引起线程等待,因此不会产生死锁。
package com.jingchu.juc.relock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @description: 公平竞争锁(先来后到)
* @author: JingChu
* @createtime :2020-07-21 11:06:05
**/
public class LockFair implements Runnable {
public static ReentrantLock reentrantLock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + "获取到锁");
}finally {
reentrantLock.unlock();
}
}
}
public static void main(String[] args) {
LockFair lockFair = new LockFair();
Thread t1 = new Thread(lockFair,"A");
Thread t2 = new Thread(lockFair,"B");
t1.start();
t2.start();
}
}
运行结果:
必然是AB循环获得锁,如果没有公平锁的话,有可能连续的A或者连续的B。
lock();//获得锁,如果锁被占用,则等待。
lockInterruptibly();//获得锁,有限响应中断
tryLock();//尝试获得锁,成功true,失败false,不等待。
tryLock(Long time,TimeUnit unit);//在给定时间内尝试获得锁
unlock();//释放锁
前面的博客我们已经讲过了obj.wait和obj.nofity,Condition对象和他们的作用大致相同。wait和notify和synchronized一起使用,condition和重入锁一起使用。下面直接上代码:
package com.jingchu.juc.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @description: Condition条件
* @author: JingChu
* @createtime :2020-07-21 11:22:38
**/
public class MyCondition implements Runnable {
public static ReentrantLock reentrantLock = new ReentrantLock();
public static Condition condition = reentrantLock.newCondition();
@Override
public void run() {
reentrantLock.lock();
try {
condition.await();
System.out.println("线程正在运行");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyCondition myCondition = new MyCondition();
Thread t1 = new Thread(myCondition);
t1.start();
Thread.sleep(2000);
reentrantLock.lock();
condition.signal();
reentrantLock.unlock();
}
}
首先在run方法内,我们让线程进行等待,然后释放锁。在main方法中他出通知,并给予锁。程序才能继续运行。
Codition提供的基本方法如下:
await() throws InterruptedException;//使当前线程等待,同时释放当前锁,当其他线程调动signal()或者signalAll()方法时,线程会重新执行,类似wait()和notify()
awaitUninterruptibly();//基本于awati方法相同,但是不会咋等待中响应中断
sigal();//唤醒一个等待中的线程
sigalAll();//随机唤醒一个等待中的线程。
信号量为多线程协作提供了更为强大的控制方法。广义上来说,信号量是对锁的扩展。锁都只运行访问一个资源,而信号量却可以指定过个线程,同时访问一个资源。
//JDK构造方法
Semaphore(int permits) ;//创建一个 Semaphore与给定数量的许可证和非公平公平设置。
Semaphore(int permits, boolean fair) ;//创建一个 Semaphore与给定数量的许可证和给定的公平设置。
//对象内置方法
acquire();//从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。
acquireUninterruptibly();//从这个信号灯获取许可证,阻止一个可用的。
availablePermits();//返回此信号量中当前可用的许可数。
tryAcquire();//从这个信号量获得许可证,只有在调用时可以使用该许可证。
tryAcquire(long timeout, TimeUnit unit);//如果在给定的等待时间内可用,并且当前线程尚未 到达 interrupted,则从该信号量获取许可。
package com.jingchu.juc.semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @description: 信号量测试
* @author: JingChu
* @createtime :2020-07-21 14:34:39
**/
public class MySemaphore implements Runnable {
final Semaphore semaphore = new Semaphore(5);
@Override
public void run() {
try {
semaphore.acquire();
//模拟线程逻辑代码耗时
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "线程运行结束");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
final MySemaphore mySemaphore = new MySemaphore();
for (int i = 0; i < 20; i++){
executorService.submit(mySemaphore);
}
}
}
读写锁是JDK5中提供的读写分离锁。读写分离锁可以有效的帮助减少锁竞争,以提高性能。
^ | 读 | 写 |
---|---|---|
读 | 非阻塞 | 阻塞 |
写 | 阻塞 | 阻塞 |
如果在系统中读操作的次数远远大于写操作,那么读写锁就可以发挥更大的作用,提供系统的性能。
package com.jingchu.juc.write.read.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @description: 读写锁测试
* @author: JingChu
* @createtime :2020-07-21 14:53:37
**/
public class MyReadWriteLock {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = reentrantReadWriteLock.readLock();
private static Lock writeLock = reentrantReadWriteLock.writeLock();
private int value;
public Object read(Lock lock) {
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return value;
}
public void write(Lock lock, int index) {
lock.lock();
try {
Thread.sleep(1000);
value = index;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
myReadWriteLock.read(readLock);
myReadWriteLock.read(lock);
}
};
Runnable writeRunnable = new Runnable() {
@Override
public void run() {
myReadWriteLock.write(writeLock, (int) (System.currentTimeMillis() % 1000));
myReadWriteLock.write(lock, (int) (System.currentTimeMillis() % 1000));
}
};
for (int i = 0; i < 9; i++) {
new Thread(readRunnable).start();
}
new Thread(writeRunnable).start();
}
}
上述代码写了两个线程类,分别约执行1秒钟,他们分别对应读和写。使用读写锁实际运行时间约为1秒钟,但是如果我们使用普调的锁代替读写锁,将约耗时10秒或者更长
CountDownLatch在多线程中的意思是倒计时器,就是说倒计时结束以后线程开始运行
package com.jingchu.juc.count.down;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @description: 倒计时器测试
* @author: JingChu
* @createtime :2020-07-21 16:15:40
**/
public class MyCountDown implements Runnable {
static final CountDownLatch end = new CountDownLatch(5);
static final MyCountDown myCountDown = new MyCountDown();
@Override
public void run() {
try {
Thread.sleep(100);
System.out.println("检查子任务");
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i <5 ; i++) {
executorService.submit(myCountDown);
}
//等待检查
end.await();
//执行任务
executorService.shutdown();
System.out.println("开始最终任务");
}
}
CyclicBarrier是另外一种多线程并发控制使用工具。和CountDownLatch非常相似,它也可以实现线程间的计数等待,但是他的功能比CountDownLatch更加强大,也更加复杂。
CyclicBarrier翻译为循环栅栅。栅栏(Barrier)在我们生活中是为了阻碍一些行为,比如羊圈的栅栏是为了阻碍随便初入。循环(Cyclic)就不用笔者多过解释了。在这里的意思就是这个计数器可以反复使用。比如我们要求4个员工给可乐装箱,每24瓶一箱,每次得到4个人以后就将计数器归零,然后等着凑齐下一批4个员工。
package com.jingchu.juc.cyclic.barrier;
import com.sun.org.apache.xpath.internal.operations.Bool;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @description: 循环栅栏测试
* @author: JingChu
* @createtime :2020-07-21 16:35:33
**/
public class MyCyclicBarrier {
public static class ColaMan implements Runnable{
private String colaMan;
private final CyclicBarrier cyclicBarrier;
public ColaMan(String colaMan, CyclicBarrier cyclicBarrier) {
this.colaMan = colaMan;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
//等待所有可乐凑齐
cyclicBarrier.await();
packing();
//等待所有可乐装箱完毕
cyclicBarrier.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void packing(){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(colaMan+":装箱完毕");
}
}
public static class Boss implements Runnable{
Boolean flag;
int n;
public Boss(Boolean flag, int n) {
this.flag = flag;
this.n = n;
}
@Override
public void run() {
if(flag){
System.out.println("老板要求:"+n+"个员工给可乐装箱,任务完成");
}else {
System.out.println("老板要求:"+n+"个员工给可乐装箱,员工集合");
flag=true;
}
}
}
public static void main(String[] args) {
final int n = 4;
Thread[] allMan = new Thread[n];
boolean flag = false;
CyclicBarrier cyclic = new CyclicBarrier(n,new Boss(flag,n));
System.out.println("召集员工");
for (int i=0;i<n;i++){
System.out.println("员工"+i+"报道");
allMan[i] = new Thread(new ColaMan("员工号"+i,cyclic));
allMan[i].start();
}
}
}
用于创建锁和其他同步类的基本线程阻塞原语。 这个工具类可以再任何地方阻塞线程。
还记得上一章中的suspend()永久卡死线程的例子吗?,我们可以用线程阻塞类重写一下
package com.jingchu.juc.lock.support;
import java.util.concurrent.locks.LockSupport;
/**
* @description: 线程阻塞工具类测试
* @author: JingChu
* @createtime :2020-07-21 16:59:16
**/
public class LockSupportDemo {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super(name);
}
@Override public void run() {
synchronized (u) {
System.out.println("in " + getName());
LockSupport.park();
if (Thread.currentThread().isInterrupted()) {
System.out.println("被中断了");
}
System.out.println("继续执行");
}
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(1000);
t2.start();
Thread.sleep(3000);
t1.interrupt();
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}
我们发现程序正常运行了。这是因为工具类类似信号量的机制,它为每个线程准备了一个许可,如果许可可用,那么park函数会立即返回,而且会讲许可变为不可用,不可用后就会阻塞。而unpark则使得一个许可变成可用,和信号量不同的是,许可不可以得加,你不能拥有超过一个许可,它最多只有1个
下面看一下这个工具类的主要方法:
public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程
public static Object getBlocker(Thread t);
我们都知道,当线程运行结束以后一般都会自动销毁,这是一个很耗费资源的过程。为了避免系统频繁的创建和销毁线程,我们应该尽可能的让创建的线程可进行复用。这就类似于数据库连接池操作,为了避免每次查询都重新建立连接,查询结束后销毁连接,我们可以使用数据库连接池维护一些数据库连接,让他们长期保持在激活状态。线程池也是类似的理念,简单的来说,使用了线程池后,创建线程变成了从线程池中获取空闲线程,
我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。
1、Executor框架包括3大部分:
(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;
(2)任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口 的ExecutorService接口。
(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。
文章https://www.cnblogs.com/liwangcai/p/11884690.html对Executor框架的讲解十分细致,我就不过多讲述了
ThreadPoolExecutor的最后一个参数指定了拒绝策略,也就是当任务数量超过了系统实际承载的能力时的处理策略。
JDK内置了四种拒绝策略:
通过前面的介绍,我们知道线程池的主要作用就是为了线程的复用,那么线程从何而来呢?答案就是ThreadFactory。
自定义线程池可以帮助我们做很多事情,比如:我们可以追中线程池在核实创建了多少线程,也可以自定义线程的名称、组等信息,甚至可以设置线程为守护线程。总而言之,自定义线程池非常的自由。
package com.jingchu.juc.thread.factory;
import java.util.concurrent.*;
/**
* @description: 自定义线程池的测试
* @author: JingChu
* @createtime :2020-07-21 17:30:03
**/
public class MyThreadFactory {
public static class MyTask implements Runnable {
@Override
public void run() {
System.out.println("**********:" + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MICROSECONDS,
new SynchronousQueue<Runnable>(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("AAAA");
System.out.println("c创建线程"+thread);
return thread;
}
});
for (int i = 0; i < 5; i++) {
executorService.submit(task);
}
Thread.sleep(1000);
}
}
虽然JDK已经为我们提供了很方便的线程池,但是我们有些业务场景仍然需要对线程池进行扩展,比如我们想监控线程的运行时间,查看开始时间和结束时间等。
ThreadPoolExecutor是一格可以扩展的线程池,它提供了beforeExecture()和afterExecute()和terminated()三个接口对线程池进行控制。下面请看例子:
package com.jingchu.juc.extend.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @description: 扩展线程池测试
* @author: JingChu
* @createtime :2020-07-21 17:40:18
**/
public class MyPool {
public static class MyTask implements Runnable {
public String name;
public MyTask(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("正在执行" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MICROSECONDS,
new LinkedBlockingDeque<Runnable>()) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("准备执行" + ((MyTask) r).name);
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("结束执行" + ((MyTask) r).name);
}
};
for (int i = 0; i < 5; i++) {
MyTask task = new MyTask("AAAAA");
executorService.execute(task);
Thread.sleep(10);
}
executorService.shutdown();
}
}
可以看到,所有任务执行前、执行后,以及执行结束都可以轻易的捕获,这在调试程序的时候非常有帮助。
除了提供诸多线程方法以外,JDK还为开发人员提供了很多好用的容易类,可以大大减少开发公布工作量。众所周知,
算法+数据结构
组成了我们的程序、
本小节,笔者将为大家介绍这些数据结构。
之前我们说过HashMap在并发情况下带来的问题,程序中肯定不允许出现这样的问题,我们可以使用Collections.synchronziedMap()方法来包装我们的HashMap
public static Map map = Collections.synchronizedMap(new HashMap());
List同上
public static List<String> list= Collections.synchronizedList(new LinkedList<String>());
此时生成的List对象就是线程安全的了。