关于ruby-on-rails:玩转JUC工具Java并发编程不再危机四伏

前言
  当今互联网利用广泛须要反对高并发拜访,而Java作为一种宽泛应用的编程语言,其并发编程能力对于实现高性能的利用十分重要。而Java的JUC(java.util.concurrent)并发工具就提供了许多实用的工具类和接口,能够让Java利用轻松实现高效的并发编程。
ReetrantLock
  ReentrantLock是Java提供的一个可重入锁,也是Java并发编程中最罕用的一种锁。与synchronized关键字相比,ReentrantLock具备更大的灵活性和性能,能够更好地反对并发编程的实现。
特点

可重入性:与synchronized一样,ReentrantLock也反对可重入锁,即同一个线程能够反复取得该锁,而不会呈现死锁。
偏心锁:ReentrantLock能够创立偏心锁,即依照线程申请的程序调配锁,确保等待时间最长的线程最先取得锁,防止了线程饥饿问题。
中断响应:当线程期待获取锁的过程中,能够通过调用interrupt()办法中断期待,防止线程有限期待。
条件变量:ReentrantLock能够创立多个Condition对象,用于控制线程期待和唤醒,从而实现更灵便的线程合作。
性能优越:在高度竞争的多线程环境中,ReentrantLock相比synchronized有更好的性能体现,特地是在多处理器零碎中。

简略应用
模仿秒杀商品场景
public class SecKillDemo {

private static int stock = 1;

// 秒杀锁
private static final ReentrantLock lock = new ReentrantLock();

public static void main(String[] args) {
    // 模仿多个用户抢购
    for (int i = 0; i < 10; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 加锁
                lock.lock();
                try {
                    // 判断库存是否足够
                    if (stock > 0) {
                        // 模仿生成订单
                        System.out.println(Thread.currentThread().getName() + "抢购胜利,生成订单");
                        // 缩小库存
                        stock--;
                    } else {
                        System.out.println(Thread.currentThread().getName() + "抢购失败,库存有余");
                    }
                } finally {
                    // 解锁
                    lock.unlock();
                }
            }
        }).start();
    }
}

}
复制代码
加锁后果:最先进来的线程抢购胜利其余线程抢购失败

不加锁后果:4个线程抢购胜利呈现超卖景象

可重入示例
  当一个线程取得了ReentrantLock锁之后,如果该线程持续申请取得这个锁,那么该线程能够持续取得这个锁,这种状况就是ReentrantLock锁的可重入性。上面是一个体现出ReentrantLock锁的可重入性的例子:
public class ReentrantLockExample {

private final ReentrantLock lock = new ReentrantLock();

public void outer() {
    lock.lock(); // 第一次获取锁
    try {
        inner();
    } finally {
        lock.unlock(); // 开释锁
    }
}

public void inner() {
    lock.lock(); // 第二次获取锁
    try {
        System.out.println("第二次获取锁");
    } finally {
        lock.unlock(); // 开释锁
    }
}

public static void main(String[] args) {
    ReentrantLockExample example = new ReentrantLockExample();
    example.outer();
}

}
复制代码
  在这个例子中,咱们定义了一个ReentrantLock锁,并创立了一个outer办法和一个inner办法。在outer办法中,咱们首先通过调用lock办法获取锁,并在try块中调用inner办法,而后在finally块中开释锁。在inner办法中,咱们再次获取锁,并输入一条信息。
  能够看到,在outer办法中,咱们第一次获取锁,而后调用inner办法,inner办法中又通过lock办法获取了锁,但这次获取锁是胜利的,并且可能失常输入信息,阐明ReentrantLock锁具备可重入性。
Condition
  当应用ReentrantLock时,能够应用Condition对象来进行线程的协调和期待。
public class ConditionDemo {

private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int count = 0;

public void increment() {
    lock.lock();
    try {
        while (count == 1) {  // 如果计数器曾经达到1,就期待
            condition.await();
        }
        count++;  // 计数器加1
        System.out.println(Thread.currentThread().getName() + ": " + count);
        condition.signalAll();  // 唤醒其余期待的线程
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

public void decrement() {
    lock.lock();
    try {
        while (count == 0) {  // 如果计数器曾经为0,就期待
            condition.await();
        }
        count--;  // 计数器减1
        System.out.println(Thread.currentThread().getName() + ": " + count);
        condition.signalAll();  // 唤醒其余期待的线程
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

public static void main(String[] args) {
    ConditionDemo demo = new ConditionDemo();

    // 创立两个线程别离进行减少和缩小操作
    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            demo.increment();
        }
    }, "Thread-A").start();

    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            demo.decrement();
        }
    }, "Thread-B").start();
}

}
复制代码
  在这个示例中,有一个计数器count,两个线程别离进行减少和缩小操作。在increment()和decrement()办法中,通过调用ReentrantLock的lock()办法获取锁,并在执行操作之前应用while循环判断条件是否满足,如果不满足就通过调用Condition的await()办法期待。
  当条件满足时,线程执行相应的操作,并应用Condition的signalAll()办法唤醒其余期待的线程。在这个示例中,当计数器为1时,减少线程就会期待,直到计数器减为0;当计数器为0时,缩小线程就会期待,直到计数器减少为1。
利用场景

解决多线程竞争资源的问题,例如多个线程同时对同一个数据库进行写操作,能够应用ReentrantLock保障每次只有一个线程可能写入。

实现多线程工作的程序执行,例如在一个线程执行完某个工作后,再让另一个线程执行工作。

实现多线程期待/告诉机制,例如在某个线程执行完某个工作后,告诉其余线程继续执行工作

Semaphore
  Semaphore(信号量)是JUC并发工具包中的一种同步工具,用于治理一个或多个共享资源的拜访。Semaphore 保护一个计数器,该计数器能够对共享资源的拜访进行管制,相似于停车场的车位治理,当所有的车位已满时,新来的车辆必须期待其余车辆来到能力进入停车场。
Semaphore实现限流
public class RateLimiter {

private Semaphore semaphore;

public RateLimiter(int permits) {
    semaphore = new Semaphore(permits);
}

public boolean tryAcquire() {
    return semaphore.tryAcquire();
}

public void release() {
    semaphore.release();
}

}
复制代码
  在上述代码中,咱们定义了一个名为RateLimiter的限流器类。它有一个构造函数,用于初始化Semaphore计数器,其中参数permits示意容许同时拜访的线程数。
  在tryAcquire()办法中,咱们应用了Semaphore的tryAcquire()办法来尝试获取一个许可,如果获取胜利则返回true,否则返回false。
  在release()办法中,咱们调用Semaphore的release()办法来开释一个许可。
public static void main(String[] args) {

int permits = 5;
RateLimiter rateLimiter = new RateLimiter(permits);

// 模仿10个线程并发申请
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            if (rateLimiter.tryAcquire()) {
                System.out.println(Thread.currentThread().getName() + " 取得拜访权限");
                Thread.sleep(1000);
            } else {
                System.out.println(Thread.currentThread().getName() + " 被限流");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rateLimiter.release();
            System.out.println(Thread.currentThread().getName() + " 开释拜访权限");
        }
    }, "Thread-" + (i + 1)).start();
}

}
复制代码
在上述代码中,咱们创立了一个限流器RateLimiter,容许同时有5个线程拜访。而后,咱们模仿10个申请。当有可用许可时,申请被容许拜访;当所有许可都被占用时,申请被回绝拜访。
利用场景

限流:Semaphore能够用于限度对共享资源的并发拜访数量,以控制系统的流量。

资源池:Semaphore能够用于实现资源池,以保护一组无限的共享资源。

CountDownLatch
  CountDownLatch能够帮忙控制线程之间的执行程序。在某些场景下,可能须要期待多个线程执行结束后,再继续执行某些操作,这时候就能够应用CountDownLatch来实现线程的期待。
  CountDownLatch外部保护了一个计数器,该计数器的初始值能够通过构造函数进行指定。在主线程中调用CountDownLatch的await()办法会使以后线程期待,直到计数器的值为0时才会继续执行。而在其余线程中调用CountDownLatch的countDown()办法则会将计数器的值减1。当计数器的值减到0时,之前在主线程中调用await()办法的线程就会继续执行。
CountDownLatch实现多任务合并
  比如说,有一个零碎须要进行批量数据导入,数据是从多个文件中读取的,每个文件须要一个线程进行解决,等所有文件处理完毕后再进行下一步操作,这时候就能够应用CountDownLatch来实现期待多个线程执行结束后再继续执行。。
public class BatchImportDemo {

private CountDownLatch latch;
private String[] filenames = {"file1.txt", "file2.txt", "file3.txt"};

public BatchImportDemo() {
    this.latch = new CountDownLatch(filenames.length);
}

public void start() {
    for (String filename : filenames) {
        new Thread(new ImportTask(filename, latch)).start();
    }
    try {
        latch.await(); // 期待所有线程执行结束
        System.out.println("所有文件处理完毕,开始进行下一步操作。");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

private static class ImportTask implements Runnable {
    private String filename;
    private CountDownLatch latch;

    public ImportTask(String filename, CountDownLatch latch) {
        this.filename = filename;
        this.latch = latch;
    }

    @Override
    public void run() {
        // 解决文件的逻辑
        System.out.println("正在解决文件 " + filename);
        try {
            Thread.sleep(2000); // 模仿文件解决
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("文件 " + filename + " 处理完毕。");
        latch.countDown(); // 计数器减1
    }
}

public static void main(String[] args) {
    BatchImportDemo demo = new BatchImportDemo();
    demo.start();
}

}
复制代码
  在这个示例中,咱们定义了一个BatchImportDemo类,它蕴含了一个CountDownLatch实例和一个字符串数组filenames,示意须要解决的文件名。在start()办法中,咱们启动了多个线程来解决每个文件,并调用了latch.await()办法来期待所有线程执行结束。而在每个线程中,咱们执行了具体的文件解决逻辑,并在处理完毕后调用了latch.countDown()办法来将计数器减1。
  运行上述代码,输入后果为
正在解决文件 file1.txt
正在解决文件 file2.txt
正在解决文件 file3.txt
文件 file1.txt 处理完毕。
文件 file2.txt 处理完毕。
文件 file3.txt 处理完毕。
所有文件处理完毕,开始进行下一步操作。
复制代码
  能够看到,当所有文件处理完毕后,程序输入了“所有文件处理完毕,开始进行下一步操作。”的信息。这阐明咱们胜利地应用CountDownLatch来期待多个线程执行结束后再进行后续的操作。
利用场景

主线程期待多个线程执行结束后再继续执行。
多个线程期待某个操作实现后再继续执行。
实现并发工作的协调,例如多个线程同时执行不同的子工作,当所有子工作都执行结束后,再执行后续的操作。

CyclicBarrier
  CyclicBarrier(回环栅栏或循环屏障),是 Java 并发库中的一个同步工具,通过它能够实现让一组线程期待至某个状态(屏障点)
之后再全副同时执行。叫做回环是因为当所有期待线程都被开释当前,CyclicBarrier能够被重用。
CyclicBarrier多个线程协同工作
public class CyclicBarrierDemo {

public static void main(String[] args) throws InterruptedException {
    int threadCount = 5;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount, new Runnable() {
        @Override
        public void run() {
            System.out.println("所有线程执行实现,开始执行主线程...");
        }
    });
    for (int i = 0; i < threadCount; i++) {
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " 执行工作...");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " 执行工作实现...");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }
    System.out.println("主线程执行...");
}

}
复制代码
  上述示例中,定义了5个线程,这5个线程须要期待所有线程都执行实现后,能力继续执行主线程。在定义CyclicBarrier时,将屏障点的数量设置为5,当所有线程都达到屏障点时,会执行Runnable中的工作,输入 “所有线程执行实现,开始执行主线程…”。每个线程执行工作实现后,会调用CyclicBarrier的await()办法,期待其余线程执行实现。
  执行以上代码,输入如下:
Thread-0 执行工作…
Thread-1 执行工作…
Thread-2 执行工作…
Thread-3 执行工作…
Thread-4 执行工作…
主线程执行…
Thread-1 执行工作实现…
Thread-0 执行工作实现…
Thread-3 执行工作实现…
Thread-2 执行工作实现…
Thread-4 执行工作实现…
所有线程执行实现,开始执行主线程…
复制代码
  从输入后果能够看出,所有线程都先执行各自的工作,而后期待其余线程执行实现,当所有线程都执行实现后,执行Runnable中的工作,输入 “所有线程执行实现,开始执行主线程…”,最初主线程继续执行。
利用场景

多线程工作:CyclicBarrier 能够用于将简单的任务分配给多个线程执行,并在所有线程实现工作后触发后续作。

数据处理:CyclicBarrier 能够用于协调多个线程间的数据处理,在所有线程解决完数据后触发后续操作。

Phaser
  Phaser用于协调多个线程的执行。它提供了一些不便的办法来治理多个阶段的执行,能够让程序员灵便地控制线程的执行程序和阶段性的执行。Phaser能够被视为CyclicBarrier和CountDownLatch的进化版,它可能自适应地调整并发线程数,能够动静地减少或缩小参加线程的数量。所以Phaser特地适宜应用在反复执行或者重用的状况。
Phaser阶段工作应用
public class PhaserDemo {

public static void main(String[] args) {
    int numPhases = 3; // 设置阶段数为3
    int numThreads = 5; // 设置线程数为5
    Phaser phaser = new Phaser(numThreads); // 创立Phaser对象并注册线程数
    for (int i = 0; i < numThreads; i++) {
        new Thread(new Worker(phaser)).start(); // 创立并启动线程
    }
    for (int i = 0; i < numPhases; i++) {
        System.out.println("Phase " + i + " 阶段开始");
        phaser.arriveAndAwaitAdvance(); // 期待所有线程达到同步点
        System.out.println("Phase " + i + " 阶段实现");
    }
}

static class Worker implements Runnable {
    private Phaser phaser;

    Worker(Phaser phaser) {
        this.phaser = phaser;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 开始执行");
        for (int i = 0; i < 3; i++) { // 模仿每个线程须要执行3个工作
            phaser.arriveAndAwaitAdvance(); // 达到同步点并期待其余线程
            System.out.println(Thread.currentThread().getName() + " 实现第" + i + "个工作");
        }
    }
}

}
复制代码
  上述示例中,咱们创立了一个Phaser对象,将线程数和阶段数别离设置为5和3,而后创立5个线程并启动它们。每个线程须要实现3个工作,在实现每个工作后调用arriveAndAwaitAdvance()办法达到同步点并期待其余线程,等到所有线程达到同步点后才会进入下一个阶段。最终,程序输入了每个线程实现工作的信息,以及每个阶段的开始和完结工夫。
利用场景

多线程执行多阶段工作,须要协调各个线程的执行程序。
多线程进行游戏或模仿操作,须要协调各个线程的执行机会。
多个线程须要协同工作来解决一个大型问题,例如搜索算法或者数据分析等。

Exchanger
  Exchanger是JUC(java.util.concurrent)并发工具之一,它提供了一个同步点,使得两个线程能够替换对象。Exchanger中替换对象的过程是一个阻塞办法,只有在两个线程都达到同步点时,才会替换对象,并且在替换实现后,两个线程会继续执行本人的代码。
  Exchanger通常用于实现数据的同步和线程间的通信,例如在生产者和消费者模式中,能够应用Exchanger来实现生产者和消费者之间的数据交换。另外,Exchanger也能够用于遗传算法、数据加密等利用场景中。
Exchanger应用
public class ExchangerDemo {

public static void main(String[] args) {
    Exchanger<String> exchanger = new Exchanger<>();
    Thread thread1 = new Thread(() -> {
        try {
            String data1 = "data1";
            String data2 = exchanger.exchange(data1);
            System.out.println("Thread1 received: " + data2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    Thread thread2 = new Thread(() -> {
        try {
            String data2 = "data2";
            String data1 = exchanger.exchange(data2);
            System.out.println("Thread2 received: " + data1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    thread1.start();
    thread2.start();

}

}
复制代码
  在上述示例代码中,创立了一个Exchanger对象exchanger,而后启动了两个线程thread1和thread2。线程thread1将字符串”data1″替换给线程thread2,并且接管到线程thread2替换过去的字符串”data2″;线程thread2将字符串”data2″替换给线程thread1,并且接管到线程thread1替换过去的字符串”data1″。最初,两个线程都输入了接管到的数据。
利用场景

生产者消费者模式:在生产者和消费者模式中,能够应用Exchanger来实现生产者和消费者之间的数据交换,从而实现数据的同步和交换。
遗传算法:在遗传算法中,能够应用Exchanger来实现父代和子代之间的基因替换,从而实现基因的进化和优化。
数据加密:在数据加密中,能够应用Exchanger来实现加密和解密数据之间的替换,从而保证数据的安全性。
线程间合作:在须要多个线程合作实现某项工作的场景中,能够应用Exchanger来实现线程间的数据交换和同步,从而协同实现工作。

  须要留神的是,Exchanger只实用于两个线程之间的数据交换,如果须要多个线程之间的数据交换和同步,能够应用其余的并发工具,例如CyclicBarrier、CountDownLatch等。在抉择并发工具时,应依据具体的场景和需要来进行抉择。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理