前言:

JUC中提供了很多同步工具类,比如CountDownLatch、CyclicBarrier、Semaphore等,都可以作用同步手段来实现多线程之间的同步效果

一、CountDownLatch

1.1、CountDownLatch的使用

CountDownLatch可以理解为是同步计数器,作用是允许一个或多个线程等待其他线程执行完成之后才继续执行,比如打dota、LoL或者王者荣耀时,创建了一个五人房,只有当五个玩家都准备了之后,游戏才能正式开始,否则游戏主线程会一直等待着直到玩家全部准备。在玩家没准备之前,游戏主线程会一直处于等待状态。如果把CountDownLatch比做此场景都话,相当于开始定义了匹配游戏需要5个线程,只有当5个线程都准备完成了之后,主线程才会开始进行匹配操作。

CountDownLatch案例如下:

 public static void countDownLatchTest() throws Exception{
CountDownLatch latch = new CountDownLatch(5);//定义了需要达到条件都线程为5个线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<12; i++){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
long count = latch.getCount();
latch.countDown();/**相当于准备游戏成功*/
if(count > 0) {
System.out.println("线程" + Thread.currentThread().getName() + "组队准备,还需等待" + latch.getCount() + "人准备");
}else {
System.out.println("线程" + Thread.currentThread().getName() + "组队准备,房间已满不可加入");
}
}
}).start();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("游戏房间等待玩家加入...");
/**一直等待到规定数量到线程都完全准备之后才会继续往下执行*/
latch.await();
System.out.println("游戏房间已锁定...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("等待玩家准备中...");
/**一直等待到规定数量到线程都完全准备之后才会继续往下执行*/
latch.await();
System.out.println("游戏匹配中...");
}

执行结果如下:

 等待玩家准备中...
游戏房间等待玩家加入...
线程Thread-2组队准备,还需等待4人准备
线程Thread-3组队准备,还需等待3人准备
线程Thread-4组队准备,还需等待2人准备
线程Thread-5组队准备,还需等待1人准备
线程Thread-6组队准备,还需等待0人准备
游戏匹配中...
游戏房间已锁定...
线程Thread-7组队准备,房间已满不可加入
线程Thread-8组队准备,房间已满不可加入
线程Thread-9组队准备,房间已满不可加入
线程Thread-10组队准备,房间已满不可加入
线程Thread-11组队准备,房间已满不可加入
线程Thread-12组队准备,房间已满不可加入
线程Thread-13组队准备,房间已满不可加入

本案例中有两个线程都调用了latch.await()方法,则这两个线程都会被阻塞,直到条件达成。当5个线程调用countDown方法之后,达到了计数器的要求,则后续再执行countDown方法的效果就无效了,因为CountDownLatch仅一次有效

1.2、CountDownLatch的实现原理

CountDownLatch的实现原理主要是通过内部类Sync来实现的,内部类Sync是AQS的子类,主要是通过重写AQS的共享式获取和释放同步状态方法来实现的。源码如下:

CountDownLatch初始化时需要定义调用count的次数,然后每调用一次countDown方法都会计数减一,源码如下:

 public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
 public void countDown() {
sync.releaseShared(1);
}
 public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

可以看出CountDownLatch的实现逻辑全部都是调用内部类Sync的对应方法实现的,Sync源码如下:

 private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
/**初始化设置计数值实际就是设置AQS的同步状态值*/
setState(count);
} int getCount() {
return getState();
} protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
} /**CountDownLatch的countDown方法的实际执行逻辑*/
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
/**当CountDownLatch的计数值为0时,表示倒计数已完成,则直接返回false*/
if (c == 0)
return false;
int nextc = c-1;
/**通过CAS操作来设置同步状态自减1操作*/
if (compareAndSetState(c, nextc))
/**返回当前同步状态值是否为0,只有当状态值为0时才返回true,否则返回false*/
return nextc == 0;
}
}
}

通过内部类Sync的源码可以分析出,CountDownLatch的实现完整逻辑如下:

1、初始化CountDownLatch实际就是设置了AQS的state为计数的值

2、调用CountDownLatch的countDown方法时实际就是调用AQS的释放同步状态的方法,每调用一次就自减一次state值

3、调用await方法实际就调用AQS的共享式获取同步状态的方法acquireSharedInterruptibly(1),这个方法的实现逻辑就调用子类Sync的tryAcquireShared方法,只有当子类Sync的tryAcquireShared方法返回大于0的值时才算获取同步状态成功,

否则就会一直在死循环中不断重试,直到tryAcquireShared方法返回大于等于0的值,而Sync的tryAcquireShared方法只有当AQS中的state值为0时才会返回1,否则都返回-1,也就相当于只有当AQS的state值为0时,await方法才会执行成功,否则

就会一直处于死循环中不断重试。

总结:

CountDownLatch实际完全依靠AQS的共享式获取和释放同步状态来实现,初始化时定义AQS的state值,每调用countDown实际就是释放一次AQS的共享式同步状态,await方法实际就是尝试获取AQS的同步状态,只有当同步状态值为0时才能获取成功。

二、CyclicBarrier

2.1、CyclicBarrier的使用

CyclicBarrier可以理解为一个循环同步屏障,定义一个同步屏障之后,当一组线程都全部达到同步屏障之前都会被阻塞,直到最后一个线程达到了同步屏障之后才会被打开,其他线程才可继续执行。

还是以dota、LoL和王者荣耀为例,当第一个玩家准备了之后,还需要等待其他4个玩家都准备,游戏才可继续,否则准备的玩家会被一直处于等待状态,只有当最后一个玩家准备了之后,游戏才会继续执行。

CyclicBarrier使用案例如下:

 public static void CyclicBarrierTest() throws Exception {
CyclicBarrier barrier = new CyclicBarrier(5);//定义需要达到同步屏障的线程数量
for (int i=0;i<12;i++){
Thread.sleep(1000L);
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("线程"+Thread.currentThread().getName()+"组队准备,当前" + (barrier.getNumberWaiting()+1) + "人已准备");
barrier.await();/**线程进入等待,直到最后一个线程达到同步屏障*/
System.out.println("线程:"+Thread.currentThread().getName()+"开始组队游戏");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}).start();
}
}

执行结果如下:

 线程Thread-0组队准备,当前1人已准备
线程Thread-1组队准备,当前2人已准备
线程Thread-2组队准备,当前3人已准备
线程Thread-3组队准备,当前4人已准备
线程Thread-4组队准备,当前5人已准备
线程:Thread-4开始组队游戏
线程:Thread-0开始组队游戏
线程:Thread-1开始组队游戏
线程:Thread-2开始组队游戏
线程:Thread-3开始组队游戏
线程Thread-5组队准备,当前1人已准备
线程Thread-6组队准备,当前2人已准备
线程Thread-7组队准备,当前3人已准备
线程Thread-8组队准备,当前4人已准备
线程Thread-9组队准备,当前5人已准备
线程:Thread-9开始组队游戏
线程:Thread-5开始组队游戏
线程:Thread-7开始组队游戏
线程:Thread-6开始组队游戏
线程:Thread-8开始组队游戏
线程Thread-10组队准备,当前1人已准备
线程Thread-11组队准备,当前2人已准备

本案例中定义了达到同步屏障的线程为5个,每当一个线程调用了barrier.await()方法之后表示该线程已达到屏障,此时当前线程会被阻塞,只有当最后一个线程调用了await方法之后,被阻塞的其他线程才会被唤醒继续执行。

另外CyclicBarrier是循环同步屏障,同步屏障打开之后立马会继续计数,等待下一组线程达到同步屏障。而CountDownLatch仅单次有效。

2.2、CyclicBarrier的实现原理

先看下CyclicBarrier的构造方法

 public CyclicBarrier(int parties) {
this(parties, null);
} public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;//同步屏障总需线程数
this.count = parties;//当前剩余需要达到的线程数
this.barrierCommand = barrierAction;
}

CyclicBarrier的构造方法没有特殊之处,主要是给两个属性parties(总线程数)、count(当前剩余线程数)进行赋值,这里需要两个值的原因是CyclicBarrier提供了重置的功能,当调用reset方法重置时就需要将count值再赋值成parties的值

再看下await方法的实现逻辑

 public int await() throws InterruptedException, BrokenBarrierException {
try {
//调用私有方法dowait方法
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
} /**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
//CyclicBarrier有个ReentrantLock属性的lock
final ReentrantLock lock = this.lock;
//加锁操作
lock.lock();
try {
final Generation g = generation; if (g.broken)
throw new BrokenBarrierException();
//响应线程中断
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//count自减操作
int index = --count;
//判断当前还需达到同步屏障的线程数是否为0
if (index == 0) { // tripped
boolean ranAction = false;
try {
//barrierCommand是同步屏障打开之后需要执行的Runnable对象
final Runnable command = barrierCommand;
if (command != null)
//如果Runnable对象不为空直接执行Runnable线程任务
command.run();
ranAction = true;
//本次同步屏障全部达成,唤醒所有线程并开始下一次同步屏障
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
} // loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
//调用Condition对象的await方法使当前线程进入等待状态
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
} if (g.broken)
throw new BrokenBarrierException(); if (g != generation)
return index; if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
} private void nextGeneration() {
// signal completion of last generation
//唤醒所有线程
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}

从源码可以看出CyclicBarrier的实现原理主要是通过ReentrantLock和Condition来实现的,主要实现流程如下:

1、创建CyclicBarrier时定义了CyclicBarrier对象需要达到的线程数count

2、每当一个线程执行了await方法时,需要先通过ReentrantLock进行加锁操作,然后对count进行自减操作,操作成功则判断当前count是否为0;

3、如果当前count不为0则调用Condition的await方法使当前线程进入等待状态;

4、如果当前count为0则表示同步屏障已经完全,此时调用Condition的signalAll方法唤醒之前所有等待的线程,并开启循环的下一次同步屏障功能;

5、唤醒其他线程之后,其他线程继续执行剩余的逻辑。

2.3、通过Synchronized和wait/notify实现CyclicBarrier

通过分析了解了CyclicBarrier是通过ReentrantLock和Condition来实现的,而ReentrantLock+Condition在使用上基本上等同于Synchronized+wait/notify,既然如此就可以通过Synchronized+wait/notify来自定义一个CyclicBarrier,话不多说,代码如下:

public class MyCyclicBarrier {

    public MyCyclicBarrier(int parties){
this.parties = parties;
this.count = parties;
} /**当前剩余数量*/
private int count; /**设置同数量*/
private int parties; /**获取当前已准备数量*/
public int getNumberWaiting(){
return parties-count;
} public int await(){
synchronized (this){
int rest = --count;
if(rest==0){
//当剩余数量为0时表示所有线程达到屏障,则重置同步屏障并唤醒所有线程
this.count = parties;
this.notifyAll();
return 0;
}
try {
//当剩余数量大于0时,线程进入等待状态
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return count;
} /**测试 Main 方法*/
public static void main(String[] args) throws InterruptedException {
MyCyclicBarrier barrier = new MyCyclicBarrier(5);//定义需要达到同步屏障的线程数量
for (int i=0;i<12;i++){
Thread.sleep(1000L);
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("线程"+Thread.currentThread().getName()+"组队准备,当前" + (barrier.getNumberWaiting()+1) + "人已准备");
barrier.await();
System.out.println("线程:"+Thread.currentThread().getName()+"开始组队游戏");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}

执行结果如下:

 线程Thread-0组队准备,当前1人已准备
线程Thread-1组队准备,当前2人已准备
线程Thread-2组队准备,当前3人已准备
线程Thread-3组队准备,当前4人已准备
线程Thread-4组队准备,当前5人已准备
线程:Thread-4开始组队游戏
线程:Thread-3开始组队游戏
线程:Thread-0开始组队游戏
线程:Thread-1开始组队游戏
线程:Thread-2开始组队游戏
线程Thread-5组队准备,当前1人已准备
线程Thread-6组队准备,当前2人已准备
线程Thread-7组队准备,当前3人已准备
线程Thread-8组队准备,当前4人已准备
线程Thread-9组队准备,当前5人已准备
线程:Thread-9开始组队游戏
线程:Thread-7开始组队游戏
线程:Thread-5开始组队游戏
线程:Thread-6开始组队游戏
线程:Thread-8开始组队游戏
线程Thread-10组队准备,当前1人已准备
线程Thread-11组队准备,当前2人已准备

可以看出实现的效果和CyclicBarrier实现的效果完全一样

三、Semaphore

3.1、Semaphore的使用

Semaphore字面意思是信号量,实际可以看作是一个限流器,初始化Semaphore时就定义好了最大通行证数量,每次调用时调用方法来消耗,业务执行完毕则释放通行证,如果通行证消耗完,再获取通行证时就需要阻塞线程直到有通行证可以获取。

比如银行柜台的窗口,一共有5个窗口可以使用,当窗口都被占用时,后面来的人就需要排队等候,直到有窗口用户办理完业务离开之后后面的人才可继续争取。模拟代码如下:

  public static void semaphoreTest() throws InterruptedException {
int count = 5;
Semaphore semaphore = new Semaphore(count);
System.out.println("初始化" + count + "个银行柜台窗口");
for (int i=0;i<10;i++){
Thread.sleep(1000L);
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("用户"+Thread.currentThread().getName()+"占用窗口");
semaphore.acquire(1);//获取许可证
/**用户办理业务需要消耗一定时间*/
System.out.println("用户"+Thread.currentThread().getName()+"开始办理业务");
Thread.sleep(5000L);
semaphore.release();//释放许可证
System.out.println("用户"+Thread.currentThread().getName()+"离开窗口");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}

执行结果如下:

 初始化5个银行柜台窗口
用户Thread-0占用窗口
用户Thread-0开始办理业务
用户Thread-1占用窗口
用户Thread-1开始办理业务
用户Thread-2占用窗口
用户Thread-2开始办理业务
用户Thread-3占用窗口
用户Thread-3开始办理业务
用户Thread-4占用窗口
用户Thread-4开始办理业务
用户Thread-0离开窗口
用户Thread-5占用窗口
用户Thread-5开始办理业务
用户Thread-1离开窗口
用户Thread-6占用窗口
用户Thread-6开始办理业务
用户Thread-2离开窗口
用户Thread-7占用窗口
用户Thread-7开始办理业务
用户Thread-3离开窗口
用户Thread-8占用窗口
用户Thread-8开始办理业务
用户Thread-4离开窗口
用户Thread-9占用窗口
用户Thread-9开始办理业务
用户Thread-5离开窗口
用户Thread-6离开窗口
用户Thread-7离开窗口
用户Thread-8离开窗口
用户Thread-9离开窗口 

可以看出前5个线程可以直接占用窗口,但是后5个线程需要等待前面的线程离开了窗口之后才可占用窗口。

Semaphore调用acquire方法获取许可证,可以同时获取多个,但是也需要对应的释放多个,否则会造成其他线程获取不到许可证的情况。一旦许可证被消耗完,那么线程就需要被阻塞,直到许可证被释放才可继续执行。

另外Semaphore还具有公平模式和非公平模式两种用法,公平模式则遵循FIFO原则先排队的线程先拿到许可证;非公平模式则自行争取。

3.2、Semaphore实现原理

Semaphore的构造方法

 public Semaphore(int permits) {
//非公平模式
sync = new NonfairSync(permits);
} public Semaphore(int permits, boolean fair) {
//公平模式
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

构造方法只有两个参数,一个是许可证总数量,一个是是否为公平模式;默认是非公平模式

Semaphore的实现全部是通过其内部类Sync来实现了,Sync也是AQS的子类,Semaphore的实现方式基本上和ReentrantLock的实现原理如出一辙。

公平模式实现原理:

 FairSync(int permits) {
//初始化AQS的state的值
super(permits);
} protected int tryAcquireShared(int acquires) {
for (;;) {
//当首节点的后继节点不是当前线程时直接return -1
if (hasQueuedPredecessors())
return -1;
//获取当前AQS的state值
int available = getState();
//将state减去需要占用的许可证数量得到剩余的许可证数量
int remaining = available - acquires;
/**
* 当remaining<0时返回remaining则表示获取许可证失败,会进入AQS的死循环不停重试
* 当remain>0时,并且CAS成功了则返回remaining,表示获取许可证成功了
* */
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

公平模式就是当当前线程是AQS同步队列首节点的后继节点时才有权利尝试获取共享式的同步状态,并将同步状态值减去需要占用的许可证数量,如果剩余许可证数量小于0则表示获取失败进入AQS的死循环不停重试;

如果许可证数量大于0并且CAS设置成功了,则返回剩余许可证数量表示抢占许可证成功;

非公平模式实现原理:

看我公平模式的实现基本是就可以猜到非公平模式是如何实现的,只是会少了一步判断当前节点是否是首节点的后继节点而已。

 static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L; NonfairSync(int permits) {
super(permits);
} protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
} final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

了解完Semaphore的公平模式和非公平模式的占有许可证的方法,再分析释放许可证的方法,不过可以先自行猜测下会是如何实现的,既然获取许可证是通过state字段不断减少来实现的,那么毫无疑问释放许可证就肯定是不断给state增加来实现的。

释放许可证源码如下:

 /**尝试释放许可证*/
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}

Semaphore的释放许可证实际就是调用AQS的共享式释放同步状态的方法,然后调用内部类Sync重写的AQS的tryReleaseShared方法,实现逻辑就是不停CAS设置state的值加上需要释放的数量,直到CAS成功。这里少了AQS的逻辑解析,有兴趣可自行回顾AQS的共享式释放同步状态的实现原理。

四、Extra Knowledge

4.1、CountDownLatch 和 CyclicBarrier的区别?

CountDownLatch和CyclicBarrier实现的效果看似都是某个线程等待一组线程达到条件之后才可继续执行,但是实际上两者存在很多区别。

1、CountDownLatch阻塞的是调用await()的线程,不会阻塞达到条件的线程;CyclicBarrier阻塞的是达到同步屏障的所有线程

2、CountDownLatch采用倒数计数,定义数量之后,每当一个线程达到要求之后就减一;CyclicBarrier是正数计数,当数量达到定义的数量之后就打开同步屏障

3、CountDownLatch仅单次有效,不可重复使用;CyclicBarrir可以循环重复使用

4、CountDownLatch定义的数量和实际线程数无关,可以有一个线程执行多次countDown();CyclicBarrier定义的数量和实际线程数一致,必须由多个线程都达到要求执行才行(线程调用await()方法之后就会被阻塞,想调用多次也不行的)

5、CountDownLatch是通过内部类Sync继承AQS来实现的;CyclicBarrier是通过重入锁ReentrantLock来实现的

6、CountDownLatch不可重置;CyclicBarrier可以调用reset方法进行重置

Java并发包5--同步工具CountDownLatch、CyclicBarrier、Semaphore的实现原理解析的更多相关文章

  1. 并发包下常见的同步工具类详解(CountDownLatch,CyclicBarrier,Semaphore)

    目录 1. 前言 2. 闭锁CountDownLatch 2.1 CountDownLatch功能简介 2.2 使用CountDownLatch 2.3 CountDownLatch原理浅析 3.循环 ...

  2. 并发包下常见的同步工具类(CountDownLatch,CyclicBarrier,Semaphore)

    在实际开发中,碰上CPU密集且执行时间非常耗时的任务,通常我们会选择将该任务进行分割,以多线程方式同时执行若干个子任务,等这些子任务都执行完后再将所得的结果进行合并.这正是著名的map-reduce思 ...

  3. JAVA多线程提高十:同步工具CyclicBarrier与CountDownLatch

    今天继续学习其它的同步工具:CyclicBarrier与CountDownLatch 一.CyclicBarrier CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达某个公 ...

  4. Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo

    Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo CountDownLatch countDownLatch这个类使一个线程等待其他线程 ...

  5. Java核心知识点学习----线程同步工具类,CyclicBarrier学习

    线程同步工具类,CyclicBarrier日常开发较少涉及,这里只举一个例子,以做备注.N个人一块出去玩,相约去两个地方,CyclicBarrier的主要作用是等待所有人都汇合了,才往下一站出发. 1 ...

  6. Java并发包——线程同步和锁

    Java并发包——线程同步和锁 摘要:本文主要学习了Java并发包里有关线程同步的类和锁的一些相关概念. 部分内容来自以下博客: https://www.cnblogs.com/dolphin0520 ...

  7. CountDownLatch/CyclicBarrier/Semaphore 使用过吗?

    CountDownLatch/CyclicBarrier/Semaphore 使用过吗?下面详细介绍用法: 一,(等待多线程完成的)CountDownLatch  背景; countDownLatch ...

  8. JAVA多线程学习十三 - 同步工具CyclicBarrier与CountDownLatch

    一.CyclicBarrier CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序 ...

  9. Java并发和多线程4:使用通用同步工具CountDownLatch实现线程等待

    CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 用给定的计数 初始化 CountDownLatch.由于调用了 countDown ...

随机推荐

  1. SQL计算算数表达式的函数自定义(加减乘除)

    一.整体思路:循环遍历表达式字符串,设置一个index从第一个字符开始检测当前数字是否可以和后面的数字进行运算,如果可以运算,将两个数挑出来运算,然后用运算的结果替换原来表达式中的这两个数和符号,计算 ...

  2. LightOJ 1287 Where to Run(期望)

    题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1287 题意:给定一个n个点的无向图(0到n-1),你开始在0.你开始遍历这个图 ...

  3. Redis(一):数据结构与对象

    前言 本书是Redis设计与实现的读书笔记,旨在对Redis底层的数据结构及实现有一定了解.本书所有的代码基于Redis 3.0. 简单动态字符串 SDS Redis没有直接使用C语言中的字符串,而是 ...

  4. 用VC实现洪水攻击程序

    本文为原创,如需转载,请注明作者和出处,谢谢! 一.             什么是洪水攻击 洪水之猛.势不可挡.如果将洪水比作对计算机的攻击,那大家可以想象得出,攻击是多的猛烈. 在安全领域所指的洪 ...

  5. windows下安装Pycham2020软件

    1.在pycham官网下载安装软件https://www.jetbrains.com/pycharm/download/#section=windows 2.我下载的是64位的安装包,现在开始安装 3 ...

  6. 在Jetson TX2上捕获、显示摄像头视频

    参考文章:How to Capture and Display Camera Video with Python on Jetson TX2 与参考文章大部分都是相似的,如果不习惯看英文,可以看看我下 ...

  7. UVA352 The Seasonal War

    本文为UserUnknown原创 题目本身不难理解,就是深搜(或广搜,有可能以后会加在这里). 但是洛谷的题目中没有截到输入输出的格式,下面是我从UVA复制下来的样例: Sample input 6 ...

  8. Fiddler手机端抓包环境设置与过滤(一)

    一.PC端Fiddler设置 1.安装https 证书 打开Fiddler->Tool->Fiddler Options->HTTPS tab,勾选上并Capture HTTPS C ...

  9. Java的类锁、对象锁和方法锁

    在Java中,对于synchronized关键字,大家看到的第一反应就是这个关键字是进行同步操作的,即得名"同步锁". 当用它来修饰方法和代码块时,默认当前的对象为锁的对象,即对象 ...

  10. [转载] IE8+兼容小结

    本文分享下我在项目中积累的IE8+兼容性问题的解决方法.根据我的实践经验,如果你在写HTML/CSS时候是按照W3C推荐的方式写的,然后下面的几点都关注过,那么基本上很大一部分IE8+兼容性问题都OK ...