体验 Java 并发 api,用不同方式实现信号量锁(Semaphore)(1)

现在多cpu成为常规配置,常常要考虑用多线程来改善程序处理性能。虽然应该尽量使用无副作用的方式来设计并发程序,但终归免不了有的时候需要多线程共享一个或者多个数据,并可能同时修改共享数据的状态。所以,对java的并发处理有进一步的理解可以帮助我进行多线程的设计。

Java 提供了很多并发关键字和 api 类库,我学习孔乙己,用几种方式来完成一个常见的问题:同一时刻只允许几个线程在执行任务。通过对不同api的使用来加深理解。

这里列出了 7 种方式,这些例子主要是为了体验 api 而不是提供设计框架,它们很多地方都不够健壮,各种方式的优缺点及性能差异也不在本文的讨论范围之内。

首先是定义一个信号量接口,并提供一个测试类来测试各种实现方式。

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

// 工作线程需要申请到信号才能执行任务
// 如果信号都发出去了,则阻塞,等到有其他线程释放信号之后再继续申请
// 阻塞可以是先停下来,等待唤醒;这样可以释放 cpu
// 也可以不停的轮询,直到有信号;这样会占住 cpu
interface IMySemaphore {
    /*
     *  if thread be interrupted.
     *  the thread should not get the permit
     *  and should not release the semaphore
     */
    public void acquire() throws InterruptedException;      // 请求信号
    public void release();                                  // 释放信号
}

// 测试运行类
public class SemaphoreTest {
    private static void println(final String msg) {
        System.out.println(msg);
    }

    // 每隔一秒打印一个点,好看清楚运行过程
    static class TimerPrint implements Runnable {
        public void run() {
            for (int i = 0; i < 35; i++) {
                println(".");
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException ie) {
                }
            }
            println("END");
        }
    }

    // 工作类,申请信号 >> 执行 >> 释放信号
    static class Worker implements Runnable {
        private final int _id;                          // 工人编号
        private final long _wait;                       // 等一会儿
        private final IMySemaphore _semaphore;          // 信号量
        private final long _lCreateTime;

        Worker(final int id, final IMySemaphore semaphore, final long now) {
            _id = id;
            _wait = (10 - _id) * 1000;
            _semaphore = semaphore;
            _lCreateTime = now;
        }

        public void run() {
            try {
                // 申请信号
                _semaphore.acquire();

                try {
                    // 申请到信号,执行对资源的访问等操作,简单的 sleep 一会儿
                    println(String.format("Worker[%d] is running...", _id));
                    Thread.currentThread().sleep(_wait);
                    long period = (System.currentTimeMillis() - _lCreateTime) / 1000;
                    println(String.format("Worker[%d] is end, %d seconds after created.", _id, period));
                } catch (InterruptedException ie) {
                    // sleep 被中断时,处理中断过程
                } finally {
                    _semaphore.release();       // 释放信号
                }
            } catch (InterruptedException ie) { // 申请信号时线程被中断
                // 通常,中断的使用场景有以下几个:
                // 点击某个桌面应用中的取消按钮时;
                // 某个操作超过了一定的执行时间限制需要中止时;
                // 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
                // 一组线程中的一个或多个出现错误导致整组都无法继续时;
                // 当一个应用或服务需要停止时。
                long period = (System.currentTimeMillis() - _lCreateTime) / 1000;
                println(String.format("Worker[%d] is interrupted, %d seconds after created.", _id, period));
                return;
            }
        }
    } // END: Worker

    // 测试方法
    // 运行的时候可以观察到每次只有两个工作线程在执行,
    // 每个 worker 执行 10 - id 的秒数,一结束就会唤醒一个等待信号的 worker 继续运行
    // 在我机器上,轮询方式大约 28 秒执行完毕;wait/notify 方式大约 31 秒执行完毕
    private static void testTimesLock(final IMySemaphore semaphore) {

        // 每隔一秒打印一个点
        new Thread(new TimerPrint()).start();

        // 启动 10 个工作线程,查看信号申请情况
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(new Worker(i, semaphore, System.currentTimeMillis()));
            threads[i].start();
        }
        try {
            Thread.currentThread().sleep(2);
        } catch (InterruptedException ie) {
        }

        // 将第九个中断掉
        threads[9].interrupt();

        // 等待所有线程结束
        for (int i = 0; i < 10; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException ie) {
            }
        }
    }

    public static void main(final String[] args) {
        // 最多两个线程申请到信号
        int max = 2;

        testTimesLock(new MySemaphore(max));
        // testTimesLock(new SyncSema(max));
        // testTimesLock(new LockSema(max));
        // testTimesLock(new AtomicSema(max));
        // testTimesLock(new AQSSema(max));
        // testTimesLock(new LockSupportSema(max));

        // 测试重入要用重入的 worker
    }
} // END: SemaphoreTest

/**
 * ----------------------------------------------------
 *  首先是最简单的方式,使用并发包中的Semaphore实现
 * ----------------------------------------------------
 */
class MySemaphore implements IMySemaphore {
    private final Semaphore _iPermits;
    MySemaphore(final int i) {
        _iPermits = new Semaphore(i);
    }
    public void acquire() throws InterruptedException {
        _iPermits.acquire();
    }
    public void release() {
        _iPermits.release();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值