需求: 启动两个线程,交替打印奇偶数。 效果如下所示

偶数线程:0
奇数线程:1
偶数线程:2
...

两个线程交替打印

无锁实现

不需要进行任何加锁,利用并发包中的AtomicInteger和volidate修饰符组合进行实现。

public class Thread_demo1 {
    private static volatile Boolean flag = true;
    private static AtomicInteger num = new AtomicInteger();
    public static CountDownLatch latch = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {
        long start =  System.currentTimeMillis();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(num.get() <= 10000){
                    if(!flag){
                        System.out.println(Thread.currentThread().getName()+": " + num.getAndIncrement());
                        flag = true;
                    }
                }
                latch.countDown();
            }
        }, "奇数线程");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {

                while(num.get() <= 10000){
                    if(flag){
                        System.out.println(Thread.currentThread().getName()+ ":" + num.getAndIncrement());
                        flag = false;
                    }
                }
                latch.countDown();
            }
        }, "偶数线程");

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

        latch.await();
        System.out.println("共耗时:"+(System.currentTimeMillis() - start) + "ms");
    }
}

加锁实现A

通过一个boolean类型的变量来限制两个线程,分别只输出奇数和偶数。

public class Thread_demo2 {
    private int count = 0;
    private final Object lock = new Object();
    public static CountDownLatch latch = new CountDownLatch(2);

    public void go() throws InterruptedException {
        long start = System.currentTimeMillis();

        Thread thread1 = new Thread(() -> {
            while (count < 10000) {
                synchronized (lock) {
                    if ((count & 1) == 0) {
                        System.out.println(Thread.currentThread().getName() + ": " + count ++);
                    }
                }
            }
            latch.countDown();
        }, "偶数线程");

        Thread thread2 = new Thread(() -> {
            while (count < 10000) {
                synchronized (lock) {
                    if ((count & 1) == 1) {
                        System.out.println(Thread.currentThread().getName() + ": " + count ++);
                    }
                }
            }
            latch.countDown();
        }, "奇数线程");
        thread1.start();
        thread2.start();

        latch.await();
        System.out.println("共耗时:"+(System.currentTimeMillis() - start) + "ms");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread_demo2 threaddemo2 = new Thread_demo2();
        threaddemo2.go();
    }
}

加锁实现B

通过同一个对象锁来实现。

public class Thread_demo3 {
    private int count = 0;
    private final Object lock = new Object();
    public static CountDownLatch latch = new CountDownLatch(2);

    public void go() throws InterruptedException {
        long begin = System.currentTimeMillis();
        new Thread(new RunnerTest(), "偶数线程").start();
        // 确保偶数线程线先获取到锁
        Thread.sleep(1);
        new Thread(new RunnerTest(), "奇数线程").start();

        latch.await();
        System.out.println(System.currentTimeMillis() - begin);
    }

    class RunnerTest implements Runnable {
        @Override
        public void run() {
            while (count < 10000) {
                synchronized (lock) {
                    // 拿到锁就打印
                    System.out.println(Thread.currentThread().getName() + ": " + count ++);
                    // 唤醒其他线程
                    lock.notifyAll();
                    try {
                        if (count < 10000) {
                            // 如果任务还没有结束,则让出当前的锁并休眠
                            lock.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            latch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread_demo3 threaddemo3 = new Thread_demo3();
        threaddemo3.go();
    }
}

多个线程交替打印

需求: n个线程,交替打印数字。 效果如下所示

线程1:0
线程2:1
线程3:2
线程1:3
线程2:4
线程3:5
线程1:6
...
public class Demo implements Runnable {
    private static final Object LOCK = new Object();
    /**
     * 当前即将打印的数字
     */
    private static int current = 0;
    /**
     * 当前线程编号,从0开始
     */
    private int threadNo;
    /**
     * 线程数量
     */
    private int threadCount;
    /**
     * 打印的最大数值
     */
    private int maxInt;

    public Demo(int threadNo, int threadCount, int maxInt) {
        this.threadNo = threadNo;
        this.threadCount = threadCount;
        this.maxInt = maxInt;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (LOCK) {
                // 判断是否轮到当前线程执行
                while (current % threadCount != threadNo) {
                    if (current > maxInt) {
                        break;
                    }
                    try {
                        // 如果不是,则当前线程进入wait
                        LOCK.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                // 最大值跳出循环
                if (current > maxInt) {
                    break;
                }
                System.out.println("thread" + threadNo + " : " + current);
                current++;
                // 唤醒其他wait线程
                LOCK.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        int threadCount = 3;
        int max = 100;
        for (int i = 0; i < threadCount; i++) {
            new Thread(new Demo(i, threadCount, max)).start();
        }
    }
}

可以看到,核心思想都是差不多的。都用的是等待通知机制。

这里我们需要注意一个问题。当threadCount(线程数量)比较大的时候,在执行notifyAll唤醒其他线程的时候,可能会出现线程抢到了锁但并不该自己执行,然后又进入wait的情况。比如现在有100个线程,现在是第一个线程在执行,他执行完之后需要第二个线程执行,但是第100个线程抢到了,发现不是自己然后又进入wait,然后第99个线程抢到了,发现不是自己然后又进入wait,然后第98,97…直到第3个线程都抢到了,最后才到第二个线程抢到同步锁,这里就会白白的多执行很多过程,虽然最后能完成目标。

解决办法多种多样,这里介绍一种基于信号量的实现方式。

public class Main {

    static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        int N = 3;
        Thread[] threads = new Thread[N];
        final Semaphore[] syncObjects = new Semaphore[N];
        for (int i = 0; i < N; i++) {
            syncObjects[i] = new Semaphore(1);
            if (i != N-1){
                syncObjects[i].acquire();
            }
        }
        for (int i = 0; i < N; i++) {
            final Semaphore lastSemphore = i == 0 ? syncObjects[N - 1] : syncObjects[i - 1];
            final Semaphore curSemphore = syncObjects[i];
            final int index = i;
            threads[i] = new Thread(new Runnable() {

                public void run() {
                    try {
                        while (true) {
                            // 第一次执行时,由于最后一个信号量并没有执行acquire,所以这里不会阻塞
                            lastSemphore.acquire();
                            System.out.println("thread" + index + ": " + result++);
                            if (result > 100){
                                System.exit(0);
                            }
                            // 释放下一个要执行的线程的
                            curSemphore.release();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            });
            threads[i].start();
        }
    }
}

评论