java多线程与高并发(四)Atomic类和线程同步新机制

1. Atomic类和线程同步新机制

这章我们来继续将Amotic的问题,然后将除了synchronized之外的锁。事实上,无锁化操作比synchronized效率更高。
下面写个程序分别说明synchronize 和longAdder,Amotic

package com.learn.thread.three;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class TestAtomicOrSynOrLongAdder {
    static long count1 = 0L;
    private static final AtomicLong count2 = new AtomicLong(0L);
    private static LongAdder count3 = new LongAdder();
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[1000];

        // AtomicLong
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() ->{
                for (int k = 0; k < 10000; k++) {
                    count2.incrementAndGet();
                }
            });
        }
        long start = System.currentTimeMillis();
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            t.join();
        }
        long end = System.currentTimeMillis();
        System.out.println("Amotice " + count2.get() + "time " + (end - start));

        // synchronized Long
        Object lock = new Object();
        for (int i = 0; i < threads.length; i++) {
            threads[i] =
                    new Thread(() -> {
                       for (int k = 0; k < 10000; k++) {
                           synchronized (lock) {
                               count1 ++;
                           }
                       }
                    });
        }
        start = System.currentTimeMillis();
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            t.join();
        }
        end = System.currentTimeMillis();
        System.out.println("Sync: " + count1 + "time " + (end - start));

        // LongAdder
        for (int i = 0; i < threads.length; i++) {
            threads[i] =
                    new Thread(() -> {
                        for (int k = 0; k < 10000; k++) {
                            count3.increment();
                        }
                    });
        }
        start = System.currentTimeMillis();
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            t.join();
        }
        end = System.currentTimeMillis();
        System.out.println("LongAdder: " + count1 + "time " + (end - start));

    }

}

至于以上各种“锁”的效率,要分情况使用。先来看这三种的优势

Amotic和synchronized的对比下,synchronzied有可能要去操作系统申请重量级锁,所以synchronized的效率是偏低的
LongAdder和Amotic对比,LongAdder的内部做了一个分段锁,类似于分段锁的概念,在它的内部的时候,会把一个值放到一个数组里,比如说数组长度为4,最开始是0,1000个线程,250个线程就放在第一个数组元素里,以此类推,每一个都网上递增算出来的结果加在一起。
先来复习一下之前将的synchronized的细节
这是有锁分级的情况,在一定情况下,synchronized是pian是效率是好的,但是如果升级为重量级锁,那么效率是低的。
执行时间短,要同步的代码量少,线程数少用CAS
执行时间长,线程数多,用系统锁

当线程数为一万个的时候

2. 复习完了synchronized,下面看看基于CAS的一些新型锁,先来讲这些锁的用法,再来说这些锁的原理

2.1. ReentrantLock

第一种就是之前讲过的可重入锁ReentrantLock,其实synchronized也是一种可重入锁,之前讲述线程synchronized概论的时候就说过方法锁里面调用方法锁或者说子类和父类synchronized(this)就是同一把锁,是会使用到同一个锁的!这就是可重入锁。
ReentrantLock 是完全可以替代synchronized的,就是把原来写synchronized的地方换写成lock.lock(),加完锁之后需要注意记得lock.unlock解锁,因为synchronized是自动解锁的,大括号执行完就结束了,lock不行,lock必须手动解锁,建议手动解锁放在try…finally里面保证最好一定要解锁,不然的话,上锁之后中间执行的过程就有问题了,死在那里,别人就永远别想拿到锁了!!!

package com.learn.thread.three;

import com.learn.thread.second.T;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestReentranLock {

    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(2L);
                System.out.println(i);

            }
        }catch (Exception ex) {

        } finally {
            lock.unlock();
        }
    }
    void m2() {
        try {
            lock.lock();
            System.out.println("m2");
        }catch (Exception ex) {

        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        TestReentranLock testReentranLock = new TestReentranLock();

        new Thread(() -> {
            testReentranLock.m1();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            testReentranLock.m2();
        }).start();
    }
}

ReentrantLock比synchronized强大的地方就是tryLock进行尝试锁定,不管是否锁定,方法都将继续执行,synchronized如果搞不定的会,就会阻塞。但是用ReentrantLock你自己就可以决定你到底要不要wait

package com.learn.thread.three;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestReentranLockTryLock {
    private static Lock lock = new ReentrantLock();
    void m1() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(i);

            }
        }catch (Exception ex) {

        } finally {
            lock.unlock();
        }
    }

    /**
     * 使用tryLock进行尝试锁定,不管锁定与否方法都将继续执行
     * 可以使用返回值来判断是否锁定,true表示加锁成功,false表示枷锁失败
     * 同样也可以指定trylock的时间
     */
    void m2() {
        // 这里不加时间参数,默认是锁一秒的
        boolean locked = lock.tryLock();
        System.out.println("m2 ... " + locked);
        if (locked) {
            System.out.println("我被锁住了,现在释放锁进入m2");
            lock.unlock();
        }
        try {
            // 这里只是加锁的时间,过了5秒以后,锁释放
            System.out.println(locked);
            locked = lock.tryLock(5, TimeUnit.SECONDS);
            System.out.println("m2 ....." + locked);
        }catch (Exception ex) {
            System.out.printf(ex.toString());
        } finally {
            // 这里如果不去判断,会异常
            if (locked) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TestReentranLockTryLock testReentranLockTryLock = new TestReentranLockTryLock();
        new Thread(() -> {
          testReentranLockTryLock.m1();
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception ex) {

        }
        new Thread(() -> {
            testReentranLockTryLock.m2();
        }).start();
    }
}

当然除了tryLock,还可以用lock.lockInterruptibly对interrupt()做出相应,可以被打断的加锁,比如说我们可以调用一个t2.interrupt()打断它的等待,让它自己可以加锁。如果有线程1上来加锁,加锁以后开始没完没了的睡,如果线程1加了锁,那么线程2就永远无法得到锁了,这时候就可以使用interrupt强制打断线程2的等待,通过异常的形式让线程2去执行。
lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常。

    void m4() {
        try {
            // 强制打断锁

            lock.lockInterruptibly();
            System.out.println("线程2 打断线程1,开始执行");
            try {
                TimeUnit.SECONDS.sleep(5);
            }catch (Exception ex) {

            }
            System.out.println("线程2 执行完成");
        }catch (Exception ex) {
            System.out.println("线程2被中断着,可以去完成其他事情");
        } finally {
            System.out.println(lock.tryLock());
            lock.unlock();
        }
    }


    public static void main(String[] args) {
        TestReentranLockTryLock testReentranLockTryLock = new TestReentranLockTryLock();
        new Thread(() -> {
            testReentranLockTryLock.m3();
        }).start();
        Thread t2 = new Thread(() -> {
            testReentranLockTryLock.m4();
        });
        t2.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception ex) {

        }
        // t2 如果
        t2.interrupt();
    }
    
    void m3() {
        try {
            lock.lock();
            System.out.println("线程1 锁住,无休止的睡眠");
            try {
                TimeUnit.SECONDS.sleep(100000000);
            }catch (Exception ex) {

            }
        }catch (Exception ex) {

        } finally {
            lock.unlock();
        }
    }

ReentrantLock还可以指定公平锁。公平锁的意思就是当我们new 一个ReentrantLock你可以传一个参数为true,这个表示公平锁,公平锁的意思是谁等在前面就让谁先执行,而不是后来了就执行。ReentrantLock默认是非公平锁

package com.learn.thread.three;

import com.learn.thread.second.T;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 测试公平锁
 */
public class TestReentrantLockTrue extends Thread{
    private static ReentrantLock lock = new ReentrantLock(true);


    @Override
    public void run() {
        for (int i = 0; i< 100; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得锁");
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TestReentrantLockTrue testReentrantLockTrue = new TestReentrantLockTrue();

        Thread t1 = new Thread(testReentrantLockTrue);
        Thread t2 = new Thread(testReentrantLockTrue);
        t1.start();
        t2.start();
    }
}

2.2.回顾ReentrantLock

首先ReentrantLock是可以替代synchronized的,本身底层就是cas
tryLock:自己来控制,控制不住锁怎么办
lockInterruptibly: 用异常的形式,取消等待
公平锁与非公平锁的等待
以后聊AQS的时候,实际上它内部用的是park和unpark,也不是全部cas,也是一个锁升级的概念,只不过这个锁升级做的比较隐匿,在你等待这个队列的时候如果你拿不到还是会进入一个阻塞状态,前面至少有一个cas状态,它不像原先就直接进入阻塞状态了。

2.3.CountDownLatch

CountDownLatch 叫倒数,Latch是门栓的意思(倒数的一个门栓, 5 ,4,3,2,1 数到了,我这个门栓就打开了)
看一下下面这个程序unsingcountDownLatch,new了100个线程,接下来,又来个100个数量的CountDownLatch,这就是设置了门栓,记录个数为1000,每一个线程结束的时候就让latch.countDown(),然后启动所有的线程,在latch.await(),最后结束。
latch.countDown()是和latch.await()连用的,countDown是看住门栓,等每个线程执行到await()的时候就会按一下CountDown是,让其在原来的基础上减1,一直到这个数字变成0的时候就会被打开,这就是它们的概念,是用来等着线程结束的
用join实际上不太好控制,必须要你线程结束了才能控制,但是如果是一个门栓的话我在线程里不听得CountDown,在一个线程里就可以控制这个门栓什么时候可以往前走,用join我只能是当前线程结束了,你才能自动往前走,用join可以,但是用countDown更加灵活

package com.learn.thread.three;

import com.learn.thread.first.T;

import java.util.concurrent.CountDownLatch;

/**
 *
 */
public class TestCountDownLatch {

    /**
     * 用CountDownLatch控制线程结束
     */
    private static void usingCountDownLacth() {
        Thread[] threads = new Thread[100];
        CountDownLatch countDownLatch = new CountDownLatch(threads.length);
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                int result = 0;
                for (int j = 0; j < 10000; j++) {
                    result += j;
                }
                System.out.println(result);
                // 看住门栓,每调用一次就减1
                countDownLatch.countDown();
                System.out.println("我看住门栓了");
            });
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        try {
            // 当countDown减为0的时候,这里就会执行了
            countDownLatch.await();
            System.out.println("我来减门栓的数量了");
        }catch (Exception ex) {

        }
        System.out.println("end Latch");
    }


    /**
     * 模拟join 不好控制线程
     */
    private static void usingJoin() {
        Thread[] threads = new Thread[100];

        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                int result = 0;
                for (int j = 0; j < 10000; j++) {
                    result += j;
                }
                System.out.println(result);
                });
        }
        for (int i = 0; i < threads.length; i++) {
            System.out.println("线程start");
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            try {
                System.out.println("执行join");
                threads[i].join();
                // 这里模拟线程结束结束之后的动作
                System.out.println("equals countDown.latch");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("end usingJoin");
    }

    public static void main(String[] args) {
         usingJoin();
        // usingCountDownLacth();
    }
}

2.4.CyclicBarrier

这个名字叫CyclicBarrier,也是一个同步工具,意思就是循环栅栏,就是什么时候人满了就把栅栏推到,全部放出去,之后栅栏又重新起来,再来人,满了,放出去再继续。

举例
CyclicBarrier的概念比如说一个复杂的操作,需要访问数据库,需要访问网络,需要方位文件。有一种方式是顺序执行,这事一种非常低的效率,还有一种方式就是并发的执行,用不同的线程去操作,并且是这三个步骤有结果了我再进行下一次操作,这时候就用到了CyclicBarrier

package com.learn.thread.three;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class TestCyclicBarrier {
    private CyclicBarrier cyclicBarrier;
    public static void main(String[] args) {
        testSout();
    }
    
    
    private static void testSout() {
        // 这里会起一个线程去执行第二个参数的内容可以为空
        CyclicBarrier cyclicBarrier = new CyclicBarrier(20, () -> {
            System.out.println("满人了");
        });
        for (int i = 0; i < 400; i++) {
            new Thread(() -> {
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

}

2.5.Phaser

Phaser更像结合了CountDownLatch和CyclicBarrier,中文意思就是阶段。
Phaser是按照不同的阶段来对线程进行执行,就是它本身是维护着一个阶段这样的一个成员变量,比如说第0个阶段,第一个阶段,每个阶段不同的时候这个线程都可以往前走,有的线程走个某个阶段就停了,有的线程一直会走到结束。
你的程序中如果说用到分好几个阶段执行,而且有个阶段必须得几个人共同参与的一种情形就可能会用到Phaser
下面我们模拟一个场景结婚,分成4个阶段,分别是到达、吃饭、离开、洞房。首先吃饭之前必须要所有客人到达婚礼线程,离开也是要所用人吃完饭才离开,但是洞房是新郎和新娘的事情,客人们必须离开。

package com.learn.thread.three.marry;

import java.util.Random;
import java.util.concurrent.Phaser;

/**
 * 人类,用来模拟每一个阶段人的操作
 */
public class Person implements Runnable {

    public Person(String name) {
        this.name = name;
    }

    private String name;

    private void eat() {
        System.out.println(this.name + "吃饭");
        // 进入栅栏阶段
        TestPhaser.phaser.arriveAndAwaitAdvance();
    }

    private void arrive() {
        System.out.println(this.name + "到达婚礼现场");
        TestPhaser.phaser.arriveAndAwaitAdvance();

    }

    private void leave() {
        System.out.println(this.name + "离开");
        TestPhaser.phaser.arriveAndAwaitAdvance();

    }

    private void hug() {
        if (name.equals("新郎") || "新娘".equals(name)) {
            System.out.println(this.name + "洞房了");
            TestPhaser.phaser.arriveAndAwaitAdvance();
        }else {
            // 其他线程都不参与,控制栅栏的个数
            TestPhaser.phaser.arriveAndDeregister();
            // 还可以往栅栏上加线程
            //TestPhaser.phaser.register();

        }
    }
    @Override
    public void run() {
        arrive();
        eat();
        leave();
        hug();
    }

    static class TestPhaser {
        static Random random = new Random();
        static MarriagePhaser phaser = new MarriagePhaser();

    }

    static class MarriagePhaser extends Phaser {

        /**
         * 线程抵达这个栅栏的时候,所有的线程都满足了这个第一个栅栏的条件了这个方法
         * 会被自动调用
         *
         * @param phase 第几个阶段,从0开始
         * @param registeredParties 这个阶段有多少线程参与
         * @return
         */
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
            switch (phase) {
                case 0:
                    System.out.println(phase + "所有人都到齐了~" + registeredParties);
                    return false;
                case 1:
                    System.out.println(phase + "所有人都吃完饭了~" + registeredParties);
                    return false;
                case 2:
                    System.out.println(phase + "所有人都离开了~" + registeredParties);
                    return false;
                case 3:
                    System.out.println(phase + "婚礼结束~ 新浪和新娘抱抱" + registeredParties);
                    return true;
                default:
                    return true;
            }
        }
    }

    public static void main(String[] args) {
        TestPhaser.phaser.bulkRegister(7);
        for (int i = 0; i < 5; i++) {
            new Thread(new Person("p" + i)).start();
        }
        new Thread(new Person("新郎")).start();
        new Thread(new Person("新娘")).start();
    }
}

分析上面的程序,我们可以看到phaser.arriveAndAwaitAdvance方法是在进入栅栏前停驻,等线程的数量达到了就会自动调用onAdvance方法,返回false说明不是最后的阶段,返回true就是说到达了最后的阶段。最后phaser.arriveAndDeregister方法是注销线程,让线程不再参与阶段的执行。

2.6.ReadWriteLock读写锁

读写锁的本质就是共享锁和排他锁,读锁就是共享锁,写锁就是排他锁,读写有很多种情况,比如说你的数据库里某条数据你放在内存里读的特别多,但是改的时候并不多。
我们先来自己定义一套读写锁
假设有两个方法,一个read方法,一个write方法,read的时候我需要往里头传一把锁,这个锁我们自己定,可以是排他锁,也可以读锁或者写锁,write的时候同样需要传这把锁,同时你传一个新值,在这里值里面传一个内容。我们模拟这个操作,读的是一个Int类型的值,读的时候上锁,设置一秒钟,完了之后read over 最后unlock,然后写锁,锁定之后睡1000秒,然后把新的值给value,write over之后解锁。
我们可以用之前的ReentrantLock进行加锁,分析一下这种情况,第一种方式就是直接new ReentrantLock传进去,主程序定义了一个Runnable对象,第一个是调用read方法,第二个是调用write方法同时往里边扔一个随机值,然后启18个读线程,启2个写线程,这个两个我要执行完的话,因为是用了ReentrantLock加锁,锁的一秒钟内不没有任何线程可以拿到锁,每一个线程执行完都要1秒钟,那么20个线程就需要20秒。
我们完全可以按功能加锁
上述无非两种功能,读和写,那么能不能读的时候,所有读操作都可以共享这把锁,写的时候不让读呢?ReentrantReadWirteLock是ReentrantLock的一种实现,可以实现上述的思想。它能分出两把锁,一把readLock,一把writeLock。这两把锁在我读的时候扔进去,因此,18个线程读是可以在一秒钟完成工作的,所以读写锁效率会大大提高
下面我们看看两种方法的效率

package com.learn.thread.three.ReentrantReadWriteLock;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestReentrantReadWriteLock {

    static Lock lock = new ReentrantLock();
    private static int value = 10;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock wirteLock = readWriteLock.writeLock();

    /**
     * 普通方式加锁
     *
     * @param lock 锁
     */
    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("取值" + value);
            System.out.println("read over");
        } catch (Exception exception) {

        } finally {
            lock.unlock();
        }
    }

    /**
     * 写锁
     *
     * @param lock 锁
     * @param v 新值
     */
    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1);
            value = v;
            System.out.println("写了" + value);
        } catch (Exception ex) {

        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        testNoReadAndWriteLock();
        //testReadAndWriteLock();
    }

    static void testNoReadAndWriteLock() {
        Runnable runnable = () -> read(lock);
        Runnable write = () -> write(lock, new Random().nextInt());
        for (int i = 0; i < 18; i++) {
            new Thread(runnable).start();
        }
        for (int i = 0; i < 2; i++) {
            new Thread(write).start();
        }
    }

    static void testReadAndWriteLock() {
        Runnable runnable = () -> read(readLock);
        Runnable write = () -> write(wirteLock, new Random().nextInt());
        for (int i = 0; i < 18; i++) {
            new Thread(runnable).start();
        }
        for (int i = 0; i < 2; i++) {
            new Thread(write).start();
        }

    }
}

第一种效率明显上比第二种慢多了

以后还写不写synchronized?分布式锁怎么实现的?
以后一般都不会用这些新锁,多数用到synchronized,只有特别特别追求效率的时候才用到这些新的锁,现在的分布式锁很多,主要有redis和ZooKeeper都可以实现分布式锁,数据库也可以实现,但是数据库实现效率就低了。
给大家讲一个简单的例子,就说秒杀这个事情,在开始秒杀之前它会从数据库读取某一个数据,比如电视机500台,只能最多销售500台,完成这件事情是前面的线程访问同一个数,最开始是0一直涨到500就结束,需要加锁,从0递增。如果是单机的,LongAdder和AtomicIntegr就可以搞定。如果是分布式的,对一个数进行上锁,redis是单线程的,所以扔在一台机器上就ok。

2.7.Semaphore

词面意思就是信号灯,可以往里边传一个数,permits是允许的数量,你可以想着有几个信号灯,灯闪烁着数字表示到底允许几个来参考我这个信号灯。
s.acquire()这个方法叫做阻塞方法,阻塞方法的意思说我大概acquire不到的话我就停在这里。acquire的意思就是得到,如果我Semaphore s = new Semaphore(1)写的是1,我取一下,acquire一下他就变成0,当变成0之后,别人是acquired不到的,然后继续执行,线程结束之后注意要s.release(),执行完该执行的时候就把他release掉,release又把0变回去1,还原化。
Semaphore的含义也是限流,比如说你在买票,Semaphore写5,就是说只能5个人同时买票。acquire的意思叫获取这把锁,线程如果想继续往下执行,必须得从Semaphore里获取一个许可,他一共有5个许可用到了0你就得给我等着。
下面举例一个场景
例如,有一个八条车道的机动车道,这里只有两个收费站,到这里,谁acquire得到其中某一个谁执行。
默认Semaphore是非公平的,new Semaphore(2, true)第二个值传true才是设置公平,公平这个事情是有一堆队列在哪儿等,大家伙过来排队。用车道和收费站来举例子,就是我们有四辆车都在等着进一个车道,当后面再来一辆车的时候,它不会抄到前面去,这才叫公平,所以说内部是有队列的,不仅内部是有队列的,本章所讲的ReentrantLock,CountDownLatch,CyclicBarrier,Phaser,ReadWriteLock,Semaphore还有后边讲到Exchanger都是用同一个队列,同一个类实现的,这个类叫做AQS。

package com.learn.thread.three;

import java.util.concurrent.Semaphore;

public class TestSemaphore {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3, true);
        new Thread(() -> {
            try {
                // 阻塞方法,如果线程acquire不到,就停在这里,等别的线程释放
                semaphore.acquire();
                System.out.println("t1 running");
                Thread.sleep(1000);
                System.out.println("t1 ending");
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                semaphore.release();
            }
        }).start();
        new Thread(() -> {
            try {
                semaphore.acquire();
                System.out.println("t2 running");
                Thread.sleep(1000);
                System.out.println("t2 ending");
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                semaphore.release();
            }
        }).start();
    }
}

2.8.Exchanger

这个Exchanger叫做交换器,是两个线程互相交换数据用的。比如说第一个线程有一个成员变量s,然后exchanger.exchange(s),第二个也是这样,t1线程名字叫t1,第二个线程名字叫t2,到最后,打印出来你会发现他们两的数据交换了。线程间通信的方式非常多,这只是其中的一种,就是线程之间交换数据用的。
Exchanger 你可以想象成一个容器,这个容器有两个值,两个线程,两个格的位置,第一个线程执行到exchanger.exchange的时候,阻塞。但是要注意我这个exchange方法的时候是往里面扔了一个值,你可以认为把t1扔到第一个格子了,然后第二个线程开始执行,也执行到exchange方法了,把t2扔到第二个格子里,接下来两个线程交换了一下,t1扔给t2,t2扔给了t1,两个线程继续往前跑。Exchanger只能是两个线程之间,交换一个东西只能两两进行
下面举一个游戏中两个人状态交换

package com.learn.thread.three;

import java.util.concurrent.Exchanger;

public class TestExchanger {
    static Exchanger exchanger = new Exchanger<>();

    public static void main(String[] args) {
        new Thread(() -> {
            String s = "t1";
            try {
                s = exchanger.exchange(s);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);
        },"线程1").start();

        new Thread(() -> {
            String s = "t2";
            try {
                s = exchanger.exchange(s);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);
        },"线程2").start();
    }
}

你可能感兴趣的:(java多线程与高并发(四)Atomic类和线程同步新机制)