Java 多线程:锁(三)

作者:Grey

原文地址:

博客园:Java 多线程:锁(三)

CSDN:Java 多线程:锁(三)

StampedLock

StampedLock其实是对读写锁的一种改进,它支持在读同时进行一个写操作,也就是说,它的性能将会比读写锁更快。

更通俗的讲就是在读锁没有释放的时候是可以获取到一个写锁,获取到写锁之后,读锁阻塞,这一点和读写锁一致,唯一的区别在于读写锁不支持在没有释放读锁的时候获取写锁

StampedLock 有三种模式:

  • 悲观读:允许多个线程获取悲观读锁。

  • 写锁:写锁和悲观读是互斥的。

  • 乐观读:无锁机制,类似于数据库中的乐观锁,它支持在不释放乐观读的时候是可以获取到一个写锁。

参考: 有没有比读写锁更快的锁?

示例代码:

悲观读 + 写锁:

package git.snippets.juc;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;
import java.util.logging.Logger; // 悲观读 + 写锁
public class StampedLockPessimistic {
private static final Logger log = Logger.getLogger(StampedLockPessimistic.class.getName());
private static final StampedLock lock = new StampedLock();
//缓存中存储的数据
private static final Map<String, String> mapCache = new HashMap<>();
//模拟数据库存储的数据
private static final Map<String, String> mapDb = new HashMap<>(); static {
mapDb.put("zhangsan", "你好,我是张三");
mapDb.put("sili", "你好,我是李四");
} private static void getInfo(String name) {
//获取悲观读
long stamp = lock.readLock();
log.info("线程名:" + Thread.currentThread().getName() + " 获取了悲观读锁" + " 用户名:" + name);
try {
if ("zhangsan".equals(name)) {
log.info("线程名:" + Thread.currentThread().getName() + " 休眠中" + " 用户名:" + name);
Thread.sleep(3000);
log.info("线程名:" + Thread.currentThread().getName() + " 休眠结束" + " 用户名:" + name);
}
String info = mapCache.get(name);
if (null != info) {
log.info("在缓存中获取到了数据");
return;
}
} catch (InterruptedException e) {
log.info("线程名:" + Thread.currentThread().getName() + " 释放了悲观读锁");
e.printStackTrace();
} finally {
//释放悲观读
lock.unlock(stamp);
} //获取写锁
stamp = lock.writeLock();
log.info("线程名:" + Thread.currentThread().getName() + " 获取了写锁" + " 用户名:" + name);
try {
//判断一下缓存中是否被插入了数据
String info = mapCache.get(name);
if (null != info) {
log.info("获取到了写锁,再次确认在缓存中获取到了数据");
return;
}
//这里是往数据库获取数据
String infoByDb = mapDb.get(name);
//将数据插入缓存
mapCache.put(name, infoByDb);
log.info("缓存中没有数据,在数据库获取到了数据");
} finally {
//释放写锁
log.info("线程名:" + Thread.currentThread().getName() + " 释放了写锁" + " 用户名:" + name);
lock.unlock(stamp);
}
} public static void main(String[] args) {
//线程1
Thread t1 = new Thread(() -> {
getInfo("zhangsan");
}); //线程2
Thread t2 = new Thread(() -> {
getInfo("lisi");
}); //线程启动
t1.start();
t2.start(); //线程同步
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

乐观读:

package git.snippets.juc;

import java.util.concurrent.locks.StampedLock;
import java.util.logging.Logger; // 乐观写
public class StampedLockOptimistic {
private static final Logger log = Logger.getLogger(StampedLockOptimistic.class.getName());
private static final StampedLock lock = new StampedLock();
private static int num1 = 1;
private static int num2 = 1; /**
* 修改成员变量的值,+1
*
* @return
*/
private static int sum() {
log.info("求和方法被执行了");
//获取乐观读
long stamp = lock.tryOptimisticRead();
int cnum1 = num1;
int cnum2 = num2;
log.info("获取到的成员变量值,cnum1:" + cnum1 + " cnum2:" + cnum2);
try {
//休眠3秒,目的是为了让其他线程修改掉成员变量的值。
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断在运行期间是否存在写操作 true:不存在 false:存在
if (!lock.validate(stamp)) {
log.info("存在写操作!");
//存在写锁
//升级悲观读锁
stamp = lock.readLock();
try {
log.info("升级悲观读锁");
cnum1 = num1;
cnum2 = num2;
log.info("重新获取了成员变量的值=========== cnum1=" + cnum1 + " cnum2=" + cnum2);
} finally {
//释放悲观读锁
lock.unlock(stamp);
}
}
return cnum1 + cnum2;
} //使用写锁修改成员变量的值
private static void updateNum() {
long stamp = lock.writeLock();
try {
num1 = 2;
num2 = 2;
} finally {
lock.unlock(stamp);
}
} public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
int sum = sum();
log.info("求和结果:" + sum);
});
t1.start();
//休眠1秒,目的为了让线程t1能执行到获取成员变量之后
Thread.sleep(1000);
updateNum();
t1.join();
log.info("执行完毕"); } }

使用 StampedLock 的注意事项

  1. 看名字就能看出来StampedLock不支持重入锁。

  2. 它适用于读多写少的情况,如果不是这种情况,请慎用,性能可能还不如synchronized

  3. StampedLock的悲观读锁、写锁不支持条件变量。

  4. 千万不能中断阻塞的悲观读锁或写锁,如果调用阻塞线程的interrupt(),会导致cpu飙升,如果希望StampedLock支持中断操作,请使用readLockInterruptibly(悲观读锁)与writeLockInterruptibly(写锁)。

CountDownLatch

类似门闩的概念,可以替代join,但是比join灵活,因为一个线程里面可以多次countDown,但是join一定要等线程完成才能执行。

其底层原理是:调用await()方法的线程会利用AQS排队,一旦数字减为0,则会将AQS中排队的线程依次唤醒。

代码如下:

package git.snippets.juc;

import java.util.concurrent.CountDownLatch;

/**
* CountDownLatch可以用Join替代
*/
public class CountDownLatchAndJoin {
public static void main(String[] args) {
useCountDownLatch();
useJoin();
} public static void useCountDownLatch() {
// use countdownlatch
long start = System.currentTimeMillis();
Thread[] threads = new Thread[100000];
CountDownLatch latch = new CountDownLatch(threads.length); for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
int result = 0;
for (int i1 = 0; i1 < 1000; i1++) {
result += i1;
}
// System.out.println("Current thread " + Thread.currentThread().getName() + " finish cal result " + result);
latch.countDown();
});
}
for (Thread thread : threads) {
thread.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis(); System.out.println("end latch down, time is " + (end - start)); } public static void useJoin() {
long start = System.currentTimeMillis(); // use join
Thread[] threads = new Thread[100000]; for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
int result = 0;
for (int i1 = 0; i1 < 1000; i1++) {
result += i1;
}
// System.out.println("Current thread " + Thread.currentThread().getName() + " finish cal result " + result);
});
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
} long end = System.currentTimeMillis(); System.out.println("end join, time is " + (end - start));
}
}

CyclicBarrier

类似栅栏,类比:满了20个乘客就发车 这样的场景。

比如:一个程序可能收集如下来源的数据:

  1. 数据库

  2. 网络

  3. 文件

程序可以并发执行,用线程操作1,2,3,然后操作完毕后再合并, 然后执行后续的逻辑操作,就可以使用CyclicBarrier

代码如下:

package git.snippets.juc;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier; /**
* CyclicBarrier示例:满员发车
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since 1.8
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(20, () -> {
System.out.println("满了20,发车");
});
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}

Semaphore

表示信号量,有如下两个操作:

s.acquire() 信号量减1

s.release()信号量加1

到 0 以后,就不能执行了,这个可以用于限流

底层原理是:如果没有线程许可可用,则线程阻塞,并通过 AQS 来排队,可以通过release()方法来释放许可,当某个线程释放了某个许可后,会从 AQS 中正在排队的第一个线程依次开始唤醒,直到没有空闲许可。

Semaphore 使用示例:有N个线程来访问,我需要限制同时运行的只有信号量大小的线程数。

代码如下:

package git.snippets.juc;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; /**
* Semaphore用于限流
*/
public class SemaphoreUsage {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);
new Thread(() -> {
try {
semaphore.acquire();
TimeUnit.SECONDS.sleep(2);
System.out.println("Thread 1 executed");
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start(); new Thread(() -> {
try {
semaphore.acquire();
TimeUnit.SECONDS.sleep(2);
System.out.println("Thread 2 executed");
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}

Semaphore可以有公平非公平的方式进行配置。

SemaphoreCountDownLatch的区别?

Semaphore 是信号量,可以做限流,限制 n 个线程并发,释放一个线程后就又能进来一个新的线程。

CountDownLatch 是闭锁,带有阻塞的功能,必须等到 n 个线程都执行完后,被阻塞的线程才能继续往下执行。

Guava RateLimiter

采用令牌桶算法,用于限流

示例代码如下

package git.snippets.juc;

import com.google.common.util.concurrent.RateLimiter;
import java.util.List;
import java.util.concurrent.Executor; /**
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/4/21
* @since
*/
public class RateLimiterUsage {
//每秒只发出2个令牌
static final RateLimiter rateLimiter = RateLimiter.create(2.0);
static void submitTasks(List<Runnable> tasks, Executor executor) {
for (Runnable task : tasks) {
rateLimiter.acquire(); // 也许需要等待
executor.execute(task);
}
}
}

注:上述代码需要引入 Guava 包

Phaser(Since jdk1.7)

遗传算法,可以用这个结婚的场景模拟: 假设婚礼的宾客有 5 个人,加上新郎和新娘,一共 7 个人。 我们可以把这 7 个人看成 7 个线程,有如下步骤要执行。

  1. 到达婚礼现场

  2. 吃饭

  3. 离开

  4. 拥抱(只有新郎和新娘线程可以执行)

每个阶段执行完毕后才能执行下一个阶段,其中拥抱阶段只有新郎新娘这两个线程才能执行。

以上需求,我们可以通过 Phaser 来实现,具体代码和注释如下:

package git.snippets.juc;

import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit; public class PhaserUsage {
static final Random R = new Random();
static WeddingPhaser phaser = new WeddingPhaser(); static void millSleep() {
try {
TimeUnit.MILLISECONDS.sleep(R.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
// 宾客的人数
final int guestNum = 5;
// 新郎和新娘
final int mainNum = 2;
phaser.bulkRegister(mainNum + guestNum);
for (int i = 0; i < guestNum; i++) {
new Thread(new Person("宾客" + i)).start();
}
new Thread(new Person("新娘")).start();
new Thread(new Person("新郎")).start();
} static class WeddingPhaser extends Phaser {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("所有人到齐");
return false;
case 1:
System.out.println("所有人吃饭");
return false;
case 2:
System.out.println("所有人离开");
return false;
case 3:
System.out.println("新郎新娘拥抱");
return true;
default:
return true;
}
}
} static class Person implements Runnable {
String name; Person(String name) {
this.name = name;
} @Override
public void run() {
// 先到达婚礼现场
arrive();
// 吃饭
eat();
// 离开
leave();
// 拥抱,只保留新郎和新娘两个线程可以执行
hug();
} private void arrive() {
millSleep();
System.out.println("name:" + name + " 到来");
phaser.arriveAndAwaitAdvance();
} private void eat() {
millSleep();
System.out.println("name:" + name + " 吃饭");
phaser.arriveAndAwaitAdvance();
} private void leave() {
millSleep();
System.out.println("name:" + name + " 离开");
phaser.arriveAndAwaitAdvance();
} private void hug() {
if ("新娘".equals(name) || "新郎".equals(name)) {
millSleep();
System.out.println("新娘新郎拥抱");
phaser.arriveAndAwaitAdvance();
} else {
phaser.arriveAndDeregister();
}
}
}
}

Exchanger

用于线程之间交换数据,exchange()方法是阻塞的,所以要两个exchange行为都执行到才会触发交换。

package git.snippets.juc;

import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit; /**
* Exchanger用于两个线程之间交换变量
*/
public class ExchangerUsage {
static Exchanger<String> semaphore = new Exchanger<>(); public static void main(String[] args) { new Thread(() -> {
String s = "T1";
try {
s = semaphore.exchange(s);
TimeUnit.SECONDS.sleep(2);
System.out.println("Thread 1(T1) executed, Result is " + s);
} catch (Exception e) {
e.printStackTrace();
}
}).start(); new Thread(() -> {
String s = "T2";
try {
s = semaphore.exchange(s);
TimeUnit.SECONDS.sleep(2);
System.out.println("Thread 2(T2) executed, Result is " + s);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}

LockSupport

其他锁的底层用的是AQS

原先让线程等待需要wait/await,现在仅需要LockSupport.park()

原先叫醒线程需要notify/notifyAll,现在仅需要LockSupport.unpark(), LockSupport.unpark()还可以叫醒指定线程,

示例代码:

package git.snippets.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport; /**
* 阻塞指定线程,唤醒指定线程
*/
public class LockSupportUsage {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
if (i == 5) {
LockSupport.park();
}
if (i == 8) {
LockSupport.park();
}
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
// unpark可以先于park调用
//LockSupport.unpark(t);
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
e.printStackTrace();
} LockSupport.unpark(t);
System.out.println("after 8 seconds");
}
}

实现一个监控元素的容器

实现一个容器,提供两个方法

// 向容器中增加一个元素
void add(T t);
// 返回容器大小
int size();

有两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束

方法 1. 使用wait + notify实现

方法 2. 使用CountDownLatch实现

方法 3. 使用LockSupport实现

代码如下:

package git.snippets.juc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport; // 实现一个容器,提供两个方法,add,size,有两个线程,
// 线程1添加10个元素到容器中,
// 线程2实现监控元素的个数,
// 当个数到5个时,线程2给出提示并结束
public class MonitorContainer { public static void main(String[] args) { useLockSupport();
// useCountDownLatch();
// useNotifyAndWait();
} /**
* 使用LockSupport
*/
private static void useLockSupport() {
System.out.println("use LockSupport...");
Thread adder;
List<Object> list = Collections.synchronizedList(new ArrayList<>());
Thread finalMonitor = new Thread(() -> {
LockSupport.park();
if (match(list)) {
System.out.println("filled 5 elements size is " + list.size());
LockSupport.unpark(null);
}
});
adder = new Thread(() -> {
for (int i = 0; i < 10; i++) {
increment(list);
if (match(list)) {
LockSupport.unpark(finalMonitor);
}
}
});
adder.start();
finalMonitor.start();
} /**
* 使用CountDownLatch
*/
private static void useCountDownLatch() {
System.out.println("use CountDownLatch...");
List<Object> list = Collections.synchronizedList(new ArrayList<>());
CountDownLatch latch = new CountDownLatch(5);
Thread adder = new Thread(() -> {
for (int i = 0; i < 10; i++) {
increment(list);
if (i <= 4) {
latch.countDown();
}
}
});
Thread monitor = new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (match(list)) {
System.out.println("filled 5 elements");
}
});
adder.start();
monitor.start();
} /**
* notify + wait 实现
*/
private static void useNotifyAndWait() {
System.out.println("use notify and wait...");
List<Object> list = Collections.synchronizedList(new ArrayList<>());
final Object o = new Object();
Thread adder = new Thread(() -> {
synchronized (o) {
for (int i = 0; i < 10; i++) {
increment(list);
if (match(list)) {
o.notify();
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("add finished");
o.notify();
}
});
Thread monitor = new Thread(() -> {
synchronized (o) {
if (match(list)) {
System.out.println("5 elements added " + list.size());
o.notify();
try {
o.wait();
System.out.println("monitor finished");
o.notify();
} catch (InterruptedException e) {
e.printStackTrace();
} }
}
});
adder.start();
monitor.start();
} /**
* 只要是5的倍数,就循环打印
*/
private static void useNotifyAndWaitLoop() {
List<Object> list = Collections.synchronizedList(new ArrayList<>());
final Object o = new Object();
Thread adder = new Thread(() -> {
synchronized (o) {
for (; ; ) {
increment(list);
if (match(list)) {
o.notify();
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread monitor = new Thread(() -> {
synchronized (o) {
while (true) {
if (match(list)) {
System.out.println("filled 5 elements");
}
o.notify();
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
adder.start();
monitor.start();
} private static void increment(List<Object> list) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
list.add(new Object());
System.out.println("list add the ele, size is " + list.size());
} private static boolean match(List<Object> list) {
return list.size() % 5 == 0;
}
}

生产者消费者问题

写一个固定容量的同步容器,拥有putget方法,以及getCount方法,能够支持 2 个生产者线程以及 10 个消费者线程的阻塞调用。

方法 1. 使用wait/notifyAll

方法 2. ReentrantLockCondition,本质就是等待队列

package git.snippets.juc;

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; // 写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。
public class ProducerAndConsumer {
public static void main(String[] args) {
// MyContainerByCondition container = new MyContainerByCondition(100);
MyContainerByNotifyAndWait container = new MyContainerByNotifyAndWait(100);
for (int i = 0; i < 25; i++) {
new Thread(container::get).start();
}
for (int i = 0; i < 20; i++) {
new Thread(() -> container.put(new Object())).start();
}
}
} // 使用ReentrantLock的Condition
class MyContainerByCondition {
static ReentrantLock lock = new ReentrantLock();
final int MAX;
private final LinkedList<Object> list = new LinkedList<>();
Condition consumer = lock.newCondition();
Condition producer = lock.newCondition(); public MyContainerByCondition(int limit) {
this.MAX = limit;
} public void put(Object object) {
lock.lock();
try {
while (getCount() == MAX) {
System.out.println("container is full");
try {
producer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(object);
consumer.signalAll();
System.out.println("contain add a object, current size " + getCount()); } finally {
lock.unlock();
} } public Object get() {
lock.lock();
try {
while (getCount() == 0) {
try {
System.out.println("container is empty");
consumer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object object = list.removeFirst();
producer.signalAll(); System.out.println("contain get a object, current size " + getCount());
return object;
} finally {
lock.unlock();
} } public synchronized int getCount() {
return list.size();
}
} // 使用synchronized的wait和notifyAll
class MyContainerByNotifyAndWait {
LinkedList<Object> list = null;
final int limit; MyContainerByNotifyAndWait(int limit) {
this.limit = limit;
list = new LinkedList<>();
} synchronized int getCount() {
return list.size();
} // index 从0开始计数
synchronized Object get() {
while (list.size() == 0) {
System.out.println("container is empty");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object o = list.removeFirst(); System.out.println("get a data");
this.notifyAll();
return o;
} synchronized void put(Object data) {
while (list.size() > limit) {
System.out.println("container is full , do not add any more");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(data); System.out.println("add a data");
this.notifyAll();
}
}

说明

本文涉及到的所有代码和图例

图例

代码

更多内容见:Java 多线程

参考资料

实战Java高并发程序设计(第2版)

深入浅出Java多线程

多线程与高并发-马士兵

Java并发编程实战

Java中的共享锁和排他锁(以读写锁ReentrantReadWriteLock为例)

【并发编程】面试官:有没有比读写锁更快的锁?

图解Java多线程设计模式

Java 多线程:锁(三)的更多相关文章

  1. “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. “全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. “全栈2019”Java多线程第三十章:尝试获取锁tryLock()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. Java多线程--锁的优化

    Java多线程--锁的优化 提高锁的性能 减少锁的持有时间 一个线程如果持有锁太长时间,其他线程就必须等待相应的时间,如果有多个线程都在等待该资源,整体性能必然下降.所有有必要减少单个线程持有锁的时间 ...

  5. synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解

    本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁 ...

  6. Java多线程的三种实现方式

    java多线程的三种实现方式 一.继承Thread类 二.实现Runnable接口 三.使用ExecutorService, Callable, Future 无论是通过继承Thread类还是实现Ru ...

  7. Java多线程(三)如何创建线程

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

  8. “全栈2019”Java多线程第三十七章:如何让等待的线程无法被中断

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  9. “全栈2019”Java多线程第三十六章:如何设置线程的等待截止时间

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 下一章 "全栈2019"J ...

  10. “全栈2019”Java多线程第三十五章:如何获取线程被等待的时间?

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. 配置svn,httpd启动报错 Job for httpd.service failed because the control process exited with error code. See "systemctl status httpd.service" and "journalctl -xe" for details.

    查看httpd的状态,发现80端口被占用,因为我的nginx的80端口. systemctl status httpd.service  解决: 把Apache的端口该成别的端口 vi /etc/ht ...

  2. 执行docker一系列命令失败

    出现这种情况之后,执行下面的命令即可. systemctl restart docker

  3. SpringCloudAlibaba分布式流量控制组件Sentinel实战与源码分析-中

    实战示例 控制台初体验 Sentinel的控制台启动后,控制台页面的内容数据都是空的,接下来我们来逐步操作演示结合控制台的使用,在上一节也已说明整合SpringCloud Alibaba第一步先加入s ...

  4. Neural Networks

    神经网络能够使用torch.nn包构建神经网络. 现在你已经对autogard有了初步的了解,nn基于autograd来定义模型并进行微分.一个nn.Module包含层,和一个forward(inpu ...

  5. docker下node环境搭建

    初始化⼀个NodeJs程序 以下操作必须已经安装了NodeJS. ⾸先创建⼀个空⽂件夹.并创建以下⽂件: server.js package.json Dockerfile .dockerignore ...

  6. CTCLoss如何使用

    CTCLoss如何使用 目录 CTCLoss如何使用 什么是CTC 架构介绍 一个简单的例子 CTC计算的推导 总概率\(p(z|x)\) 路径的含义 路径概率\(p(\pi|x)\) 什么是\(\m ...

  7. Solution -「二项式定理与组合恒等式」一些练习

    Task 1 \(\mathcal{Prob:}\) \((3x - 2y)^{18}\) 的展开式中, \(x^5y^{13}\) 的系数是什么?\(x^8y^9\) 的系数是什么? \(\math ...

  8. 开源MyBatisGenerator组件源码分析

    开源MyBatisGenerator组件源码分析 看源码前,先了解Generator能做什么? MyBatisGenerator是用来生成mybatis的Mapper接口和xml文件的工具,提供多种启 ...

  9. mysql查询版本

    系统环境下 :mysql -V; mysql内:select version();

  10. ExcelPatternTool: Excel表格-数据库互导工具

    ExcelPatternTool Excel表格-数据库互导工具 介绍: 指定Pattern文件-一个规则描述的json文档,基于此规则实现Excel表格与数据库之间的导入导出,校验等功能. 特点: ...