Leetcode编程---9种方法实现多线程交替打印

CountDownLatch

  • CountDownLatch是Java中一个多线程同步工具类,它的作用是允许一个或多个线程等待其他线程完成操作后再执行。
  • CountDownLatch包含一个计数器,初始值由构造方法指定。每当一个线程完成了需要等待的操作,它会将计数器减1。当计数器的值减为0时,所有等待的线程都会被唤醒,继续执行后续操作。
  • CountDownLatch的主要作用是协调多个线程的执行顺序,例如,一个线程需要等待其他多个线程完成某些操作后才能继续执行。它也可以用于实现多个线程之间的数据交换,例如,一个线程等待其他多个线程计算完成后将它们的结果合并。
  • CountDownLatch是一次性的,一旦计数器的值减为0后,它就不能再被重置,也不能再用于等待其他线程的操作完成。如果需要多次等待操作完成,可以使用CyclicBarrier类。
class Foo {

    CountDownLatch s2=new CountDownLatch(1);
    CountDownLatch s3=new CountDownLatch(1);

    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        s2.countDown();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        s2.await();
        printSecond.run();
        s3.countDown();
    }

    public void third(Runnable printThird) throws InterruptedException {
        s3.await();
        printThird.run(); 
    }
}

AtomicInteger

  • AtomicInteger是Java中的一个原子类,它提供了一种线程安全的方式来进行原子性的读取和更新操作。
  • AtomicInteger的主要作用是在多线程环境中对整数类型的变量进行原子性的操作,例如增加、减少、比较等。它可以确保多个线程对同一个变量进行操作时,不会出现竞争条件和数据不一致的情况。
  • AtomicInteger使用了CAS(Compare and Set)算法来实现原子性的操作。CAS算法是一种乐观锁的实现方式,它基于“先比较再修改”的思想,当多个线程同时更新同一个变量时,只有其中一个线程能够成功更新,其他线程需要重新尝试更新,直到成功为止。
  • AtomicInteger可以用于实现一些高并发的场景,例如计数器、序列生成器等。它也可以作为一种替代方案来避免使用synchronized关键字进行同步,从而提高程序的性能和可伸缩性。
  • 需要注意的是,虽然AtomicInteger可以确保多个线程对同一个变量进行操作时的线程安全,但它并不能解决所有的线程安全问题,例如复合操作的线程安全问题需要使用更高级别的同步机制来解决。
class Foo {

    AtomicInteger s2=new AtomicInteger(0);
    AtomicInteger s3=new AtomicInteger(0);

    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        s2.incrementAndGet();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        while(s2.get()!=1){};
        printSecond.run();
        s3.incrementAndGet();
    }

    public void third(Runnable printThird) throws InterruptedException {
        while(s3.get()!=1){};
        printThird.run(); 
    }
}

Semaphore

  • Semaphore是Java中的一个同步工具类,它的作用是控制同时访问某个资源的线程数量,即控制并发访问线程的数量。
  • Semaphore内部维护了一个计数器,即许可证数量,初始值可以通过构造方法进行指定。当一个线程需要访问资源时,它首先需要从Semaphore获取一个许可证,如果许可证数量大于0,则线程可以获取许可证并访问资源,同时许可证数量减1;如果许可证数量等于0,则线程需要等待其他线程释放许可证后才能获取许可证并访问资源。
  • Semaphore的主要作用是控制并发访问线程的数量,它可以用于实现资源池、限流、流量控制等场景。例如,一个数据库连接池可以使用Semaphore来控制并发访问连接的数量;一个接口服务可以使用Semaphore来限制每秒的请求量;一个爬虫程序可以使用Semaphore来控制并发抓取页面的数量。
  • 需要注意的是,Semaphore并不能保证线程安全,它只能控制并发访问线程的数量。在使用Semaphore时,需要保证访问资源的线程在访问资源之前获取到许可证,并在访问资源之后及时释放许可证,否则会出现死锁或其他线程安全问题。
class Foo {

    Semaphore s2=new Semaphore(0);
    Semaphore s3=new Semaphore(0);

    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
       printFirst.run();
       s2.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        s2.acquire();
        printSecond.run();
        s2.release();
        s3.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
       s3.acquire();
       printThird.run(); 
    }
}

Lock+ReentrantLock+Condition

  • LockReentrantLockCondition是Java中的一组同步工具,它们的作用是提供一种可重入的、可中断的、具有条件等待和公平/非公平选择特性的锁机制,用于控制多个线程的并发访问。
  • Lock是一个接口,定义了锁的基本操作,如获取锁、释放锁等。ReentrantLockLock接口的一个实现类,它提供了可重入的锁机制,即同一个线程可以多次获取同一个锁而不会死锁。ReentrantLock还提供了公平/非公平选择特性,可以控制锁的获取顺序。同时,ReentrantLock还提供了Condition接口,通过它可以实现条件等待,即线程在等待某个条件满足时可以被挂起,直到其他线程通知它们条件已经满足。
  • LockReentrantLockCondition的主要作用是提供一种高级别的同步机制,它们可以用于替代synchronized关键字进行同步,从而提高程序的性能和可伸缩性。它们还可以用于实现一些复杂的同步场景,例如读写锁、重入读写锁等。
  • 需要注意的是,LockReentrantLockCondition虽然提供了更高级别的同步机制,但使用它们时需要注意避免死锁、饥饿等问题,还需要保证同步代码块的粒度尽可能小,以避免对系统性能的影响。
class Foo {

    Lock lock=new ReentrantLock();
    Condition condition=lock.newCondition();
    int a=1;

    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        lock.lock();
        while(a==1){
            a++;
            printFirst.run();
            condition.signalAll();
        }
        lock.unlock();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        lock.lock();
        while(a!=2){
            condition.await();
        }
        a++;
        printSecond.run();
        condition.signalAll();
        lock.unlock();
    }

    public void third(Runnable printThird) throws InterruptedException {
        lock.lock();
        while(a!=3){
            condition.await();
        }
        printThird.run(); 
        lock.unlock();
    }
}

Synchronized+volatile+wait/notify

  • synchronizedvolatilewait()notify()是Java中用于实现线程同步和通信的一组关键字和方法。
  • synchronized关键字用于实现线程的互斥同步,它可以锁定某个对象或者类,使得同一时刻只有一个线程可以执行被锁定的代码块或方法。
  • volatile关键字用于实现线程的可见性和有序性,它可以保证对于被volatile修饰的变量的读取和写入操作都是原子性的,并且保证线程之间的可见性。
  • wait()notify()方法用于实现线程之间的通信和协调,它们只能在synchronized代码块或方法中使用。wait()方法会使当前线程进入等待状态,并释放锁,直到其他线程调用notify()方法或notifyAll()方法通知它继续执行;notify()方法会唤醒一个等待的线程,使其继续执行。
  • synchronizedvolatilewait()notify()的主要作用是实现线程之间的同步和通信。例如,可以使用synchronized关键字实现多个线程对共享资源的互斥访问;可以使用volatile关键字实现多个线程对共享变量的可见性和有序性;可以使用wait()notify()方法实现多个线程之间的通信和协调,例如生产者-消费者模式、线程池等。
  • 需要注意的是,使用synchronizedvolatilewait()notify()时需要避免死锁、饥饿等问题,还需要保证同步代码块的粒度尽可能小,以避免对系统性能的影响。此外,在使用wait()notify()时还需要注意线程的唤醒顺序和条件的正确性,否则会导致程序出现死锁或其他线程安全问题。
class Foo {

    private volatile int flag = 1;
    private final Object obj = new Object();

    public Foo() { }

    public void first(Runnable printFirst) throws InterruptedException {
        synchronized(obj){
            while (flag != 1){
                obj.wait();
            }
            printFirst.run();
            flag = 2;
            obj.notifyAll();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized(obj){
            while (flag != 2){
                obj.wait();
            }
            printSecond.run();
            flag = 3;
            obj.notifyAll();
        }
    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized(obj){
            while (flag != 3){
                obj.wait();
            }
            printThird.run();
        }
    }
}

CyclicBarrier

  • CyclicBarrier是Java中的一个同步工具类,它的作用是让一组线程在达到某个同步点时相互等待,直到所有线程都到达同步点后再同时继续执行。
  • CyclicBarrier可以看作是一种可重用的CountDownLatch。它内部维护了一个计数器,初始值由构造方法指定。当一个线程到达同步点时,它会调用await()方法来等待其他线程到达同步点。当所有线程都到达同步点后,CyclicBarrier会调用Runnable参数中的run()方法,并将计数器重新初始化,以便下一次使用。
  • CyclicBarrier的主要作用是控制多个线程在某个同步点上的同步,例如,一个并行计算问题可以将计算任务分配给多个线程并行执行,在计算完成后通过CyclicBarrier将计算结果合并。CyclicBarrier还可以用于实现多阶段并行计算,即在每个阶段的末尾使用CyclicBarrier来同步多个线程的执行。
  • 需要注意的是,CyclicBarrier只能在所有线程都到达同步点后继续执行,如果其中一个线程无法到达同步点,那么所有线程都将被阻塞,从而导致程序出现死锁或其他线程安全问题。在使用CyclicBarrier时需要保证所有线程都能够正常到达同步点,并且需要注意计数器的初始值和重置时机,以避免出现异常情况。
class FooBar {
    
    private int n;
    CyclicBarrier cb=new CyclicBarrier(2);
    volatile boolean done=true;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            while(!done);
        	printFoo.run();
            done=false;
            try {
                cb.await();
            } catch (BrokenBarrierException e) {}
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            try {
                cb.await();
            } catch (BrokenBarrierException e) {}
        	printBar.run();
            done=true;
        }
    }
}

LinkedBlockingQueue

  • LinkedBlockingQueue是Java中的一个阻塞队列实现类,它基于链表数据结构,提供了一个线程安全的队列,用于在多线程环境下进行生产者-消费者模式的数据交换。
  • LinkedBlockingQueue的主要作用是实现生产者-消费者模式,即多个线程之间进行数据交换。其中,生产者线程将数据放入队列中,消费者线程从队列中取出数据进行消费。LinkedBlockingQueue提供了阻塞操作,当队列为空时,消费者线程会被阻塞,直到队列中有数据可供消费;当队列已满时,生产者线程会被阻塞,直到队列中有空间可供存储。
  • LinkedBlockingQueue还提供了一些其他的操作,例如,获取队列大小、检查队列是否为空或已满等。它还提供了可选的容量限制,即在创建队列时可以指定队列的最大容量,从而限制队列中元素的数量。
  • 需要注意的是,由于LinkedBlockingQueue是一个阻塞队列,它会对生产者和消费者线程进行阻塞和唤醒操作,从而增加了线程之间的同步开销,可能会影响程序的性能和可伸缩性。在使用LinkedBlockingQueue时需要根据实际需求来选择合适的容量和阻塞策略,以及合理设计生产者和消费者线程的数量和调度策略。
public class FooBar {

    private int n;
    private BlockingQueue bar = new LinkedBlockingQueue<>(1);
    private BlockingQueue foo = new LinkedBlockingQueue<>(1);

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            foo.put(i);
            printFoo.run();
            bar.put(i);
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            bar.take();
            printBar.run();
            foo.take();
        }
    }
}

HashMap+LockSupport

  • HashMap是Java中的一个哈希表实现类,它提供了一种基于键值对的映射关系,用于存储和检索数据。LockSupport是Java中的一个线程同步工具类,它可以实现线程的阻塞和唤醒操作。
  • HashMap的主要作用是存储和检索数据,它使用哈希表数据结构来实现快速的键值对查找。HashMap的内部实现使用了链表和红黑树两种数据结构来处理哈希冲突,从而保证了查找效率和性能。HashMap还提供了一些其他的操作,例如,获取键值对数量、遍历键值对集合等。
  • LockSupport的主要作用是实现线程的阻塞和唤醒操作。它可以让一个线程等待另一个线程的信号,并在信号到达时唤醒该线程。LockSupport提供了两个主要的方法:park()方法用于阻塞当前线程,unpark(Thread thread)方法用于唤醒指定线程。
  • HashMapLockSupport可以结合使用,例如,在多线程环境下使用HashMap来存储和检索数据,并使用LockSupport来实现线程的阻塞和唤醒操作。具体地,当一个线程需要等待某个条件满足时,它可以调用LockSupport.park()方法进入等待状态,并在其他线程满足条件后调用LockSupport.unpark(Thread thread)方法来唤醒该线程。
  • 需要注意的是,使用HashMapLockSupport时需要避免死锁、饥饿等问题,还需要保证同步代码块的粒度尽可能小,以避免对系统性能的影响。此外,在使用LockSupport时还需要注意线程的唤醒顺序和条件的正确性,否则会导致程序出现死锁或其他线程安全问题。
class ZeroEvenOdd {

    private int n;
    private volatile int flag = 1;
    private Map idToThreadMap = new HashMap<>();
    
    public ZeroEvenOdd(int n) {
        this.n = n;
    }

    public void zero(IntConsumer printNumber) throws InterruptedException {
        idToThreadMap.put(0, Thread.currentThread());

        for (int i = 0; i < n; i++) {
            while (flag % 2 == 0) {
                LockSupport.park(null);
            }
            printNumber.accept(0);
            flag++;
            LockSupport.unpark(idToThreadMap.get(1));
            LockSupport.unpark(idToThreadMap.get(2));
        }
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        idToThreadMap.put(2, Thread.currentThread());

        for (int i = 2; i <= n; i += 2) {
            while (flag % 4 != 0) {
                LockSupport.park(null);
            }
            printNumber.accept(i);
            flag++;
            LockSupport.unpark(idToThreadMap.get(0));
        }
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        idToThreadMap.put(1, Thread.currentThread());

        for (int i = 1; i <= n; i += 2) {
            while (flag % 4 != 2) {
                LockSupport.park(null);
            }
            printNumber.accept(i);
            flag++;
            LockSupport.unpark(idToThreadMap.get(0));
        }
    }
}

Thread.yield()+volatile

  • Thread.yield()volatile是Java中用于实现线程同步和调度的两个关键字和方法。
  • Thread.yield()方法用于提示调度器当前线程愿意放弃CPU资源,让其他线程有更多的机会运行。调用Thread.yield()方法并不会使线程进入等待状态,而是将线程从运行状态转换为就绪状态,让调度器重新选择下一个需要运行的线程。
  • volatile关键字用于实现线程的可见性和有序性,它可以保证对于被volatile修饰的变量的读取和写入操作都是原子性的,并且保证线程之间的可见性。
  • Thread.yield()volatile的主要作用是实现线程的调度和同步。例如,可以使用Thread.yield()方法在多个线程之间实现公平竞争CPU资源的机会;可以使用volatile关键字实现多个线程对共享变量的可见性和有序性。
  • 需要注意的是,Thread.yield()方法只是一种提示,不能保证其他线程一定会得到CPU资源,具体的调度策略还需要取决于操作系统和JVM的实现。在使用volatile关键字时需要避免出现重复的读取、写入操作或者其他线程安全问题,还需要注意内存可见性问题,以及保证对于被volatile修饰的变量的读写操作都是原子性的。
class ZeroEvenOdd {

    private int n;
    private volatile int count = 1;
  
    public ZeroEvenOdd(int n) {
        this.n = n;
    }

    public void zero(IntConsumer printNumber) throws InterruptedException {
        while (count <= n * 2) {
            if (1 == (count & 1)) {
                printNumber.accept(0);
                count = count + 1;
            }
            Thread.yield();
        }
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        while (count <= n * 2) {
            if (1 == ((count >>> 1) & 1)) {
              printNumber.accept(count>>>1);
                count = count + 1;

            }
            Thread.yield();
        }
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        while (count <= n * 2) {
            if (0 == (count % 4)) {
                printNumber.accept(count>>>1);
                count = count + 1;
            }
            Thread.yield();
        }
    }
}

你可能感兴趣的:(Leetcode,在线编程,leetcode,算法,数据结构)