Java 线程&锁(三)

    public static void shortSleep(Duration duration) {
        try {
            TimeUnit.MILLISECONDS.sleep(duration.toMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

synchronized

问题一:synchronized 锁的是什么?
对象

​ 在另一篇博文 线程&Synchronized 中有介绍,synchronized 是通过改变对象头中 Mark Word 中的锁标志位来实现加锁。

synchronized-> 永远都是锁对象(Markword)

 // 锁住的是对象 o
Object o = new Object();
synchronized(o);

// 锁住的是调用这个代码块的对象
synchronized(this);

// 类锁 1
public static synchronized void method() {
    // 此处锁的是 类 -> T.class 对象
    // 对整个类(static 方法上锁)该类的所有实例在调用这个方法时都要 获取锁
}

// 类锁 2 -> 这个例子更直观地展示了锁的是该类的 .class 类对象
public void method() {
  synchronized(T.class) {
  }
}
问题二:synchronized 是否可重入?

synchronized 如果不可重入,那么根本无法实现父子类锁 -> synchronized(this) 在父类和子类中是同一把锁。

public class Father {
    public void doSth() {
        System.out.println("father wait lock");
        synchronized (this) {
            System.out.println("father get lock");
            shortSleep(Duration.ofMillis(500));
            System.out.println("father release lock");
        }
    }
}

public class Son extends Father {
    public void doOther() {
        System.out.println("son wait lock");
        synchronized (this) {
            System.out.println("son get lock");
            shortSleep(Duration.ofMillis(800));
            System.out.println("son release lock");
        }
    }

    public static void main(String[] args) {
        Son s = new Son();
      // 在同一个实例 s 中分别调用本类和父类的 synchronized(this) 方法,他们是申请同一把锁
        new Thread(s::doOther).start();
        new Thread(s::doSth).start();
    }
}

synchronized 可重入验证

public class T01_ReentrantLock {

    private synchronized void m1() {
        for (int i = 0; i < 10; i++) {
            shortSleep(Duration.ofSeconds(1));
            System.out.println(i);
            if (i == 2) {
                /* 
                	此处虽然也是使用的同一把锁,但是是可重入的
              		这里已经持有该对象锁的 m1() 方法再次调用需要获取锁的 m2() 方法可以成功
              	*/
                m2();
            }
        }
    }

    private synchronized void m2() {
        System.out.println("m2 ...");
    }

    public static void main(String[] args){
        T01_ReentrantLock rl = new T01_ReentrantLock();
        new Thread(rl::m1).start();
        shortSleep(Duration.ofSeconds(1));
        // m2 虽然已经启动,但是因获取不到锁而等待
        new Thread(rl::m2).start();
    }
}

ReentrantLock

ReentrantLock 基础

public class T02_ReentrantLock {
		// ReentrantLock 是 Java 1.5 中实现的可重入锁
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                shortSleep(Duration.ofSeconds(1));
                System.out.println(i);
                if (i == 2) {
                     /* 
                				此处虽然也是使用的同一把锁,但是是可重入的
              					这里已经持有该对象锁的 m1() 方法再次调用需要获取锁的 m2() 方法可以成功
              				*/
                    m2();
                }
            }
        } finally {
          	// 使用锁实例,最终都要释放锁
            lock.unlock();
        }
    }

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

    public static void main(String[] args){
        T02_ReentrantLock rl = new T02_ReentrantLock();
        new Thread(rl::m1).start();
        shortSleep(Duration.ofSeconds(1));
        // m2 虽然已经启动,但是因获取不到锁而等待
        new Thread(rl::m2).start();
    }
}

ReentrantLock tryLock 可设置锁的等待超时时间,但是只是尝试获取锁,到达等待时间后会返回等待的结果,但是不会报错。

public class T03_ReentrantLock {

    Lock lock = new ReentrantLock();

    private void m1() {
        try {
            lock.lock();
            // 3次能拿到锁,10次拿不到
            for (int i = 0; i < 3; i++) {
                sleep(Duration.ofSeconds(1));
                System.out.println(i);
            }
        } finally {
            lock.unlock();
        }
    }

    private void m2() {
        boolean locked = false;
        try {
            locked = lock.tryLock(5, TimeUnit.SECONDS);
            System.out.println("m2 ... " + locked);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (locked) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        T03_ReentrantLock rl = new T03_ReentrantLock();
        new Thread(rl::m1).start();
        shortSleep(Duration.ofSeconds(1));
        // m2 虽然已经启动,但是因获取不到锁而等待
        new Thread(rl::m2).start();
    }
}

ReentrantLock lock.lockInterruptibly() 获取锁,允许通过线程的 interrupt 打断该等待。

public class T04_ReentrantLock {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {
            try {
                System.out.println("t1 start");
                lock.lock();
                System.out.println("t1 locked");
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                System.out.println("t1 interrupted");
            } finally {
                lock.unlock();
            }
        });

        t1.start();
        Thread t2 = new Thread(() -> {
            boolean locked = false;
            try {
                System.out.println("t2 start");
                 lock.lock(); // 不可被打断
//                lock.lockInterruptibly();
                locked = true;
                System.out.println("t2 locked");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t2 end");
            } catch (InterruptedException e) {
                System.out.println("t2 interrupted");
            } finally {
                if (locked) {
                    lock.unlock();
                }
            }
        });
        t2.start();

        sleep(Duration.ofSeconds(2));

        // 打断线程 2 的等待
        t2.interrupt();

        // 结束线程
        sleep(Duration.ofSeconds(5));
        t1.interrupt();
    }
}

ReentrantLock 实现公平锁

公平锁 : 所有尝试获取锁的线程,在尝试获取锁时进入等待队列,按照队列中的顺序依次获取锁。如果是非公平锁,没有等待队列,所有尝试获取锁的线程会阻塞,并根据 CPU 的时间片调度来分配锁 -> 谁先抢到就是谁的,和谁先来的没有关系。

public class T05_ReentrantLock extends Thread {

    /**
     * 公平锁 - 可以看到线程1和线程2 在一定程度上 交替运行
     */
    private static ReentrantLock lock = new ReentrantLock(true);

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

    public static void main(String[] args) {
        T05_ReentrantLock tl5 = new T05_ReentrantLock();
        Thread th1 = new Thread(tl5);
        Thread th2 = new Thread(tl5);
        th1.start();
        th2.start();
    }
}

​ 之所以是在一定程度上交替运行,是因为进入等待队列的时间不一定是交替的,有可能线程1释放锁后很快又再次进入队列等锁。如果在执行时加上一点延时,便可以看到严格的依次执行。

CountDownLatch

​ 一种锁同步工具,可以控制线程的同步,效率和 join 差不多

public class T06_CountDownLatch {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        System.out.println("start: " + start);
        usingJoin();
        System.out.println("join cost: " + (System.currentTimeMillis() - start));
        start = System.currentTimeMillis();
        usingCountDownLatch();
        System.out.println("count cost: " + (System.currentTimeMillis() - start));
    }

    private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = 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;
                }
                shortSleep(Duration.ofSeconds(3));
                latch.countDown();
            });
        }

        for (Thread thread : threads) {
            thread.start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    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;
                }
                shortSleep(Duration.ofSeconds(3));
            });
        }

        for (Thread thread : threads) {
            thread.start();
        }

        for (Thread thread : threads) {
            try {
                // 在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

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

CyclicBarrier

​ 一种 可循环栅栏,当调用 barrier.await() 的线程数到达一定数量时,就会触发给定的方法。触发后重新计数。

public class T07_CyclicBarrier {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
            @Override
            public void run() {
                System.out.println("满人, 发车!");
            }
        });

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            shortSleep(Duration.ofMillis(100));
            new Thread(() -> {
                try {
                    barrier.await();
//                    System.out.println(Thread.currentThread().getName() + " 发车");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        System.out.println("cost: " + (System.currentTimeMillis() - start));
    }
}

Phaser

phaser 可以理解成一种 多步骤 的栅栏,预先定义参加该 活动 的线程数,每个线程执行完一个阶段后,会决定是执行下一个阶段还是退出。phaser 可以控制 参与本阶段的线程都执行完毕后 再一起执行下一个阶段

public class T07_Phaser {
    private static Random r = new Random();
    private static MarriagePhaser phaser = new MarriagePhaser();


    public static void main(String[] args) {

        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();
    }

    static class MarriagePhaser extends Phaser {
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
            switch (phase) {
                case 0:
                    System.out.println("所有人到齐了! " + registeredParties);
                    return false;
                case 1:
                    System.out.println("所有人吃完了! " + registeredParties);
                    return false;
                case 2:
                    System.out.println("所有人离开了! " + registeredParties);
                    return false;
                case 3:
                    System.out.println("婚礼结束了! 新郎新娘入洞房" + registeredParties);
                    return true;
                    default:
                        return true;
            }
        }
    }

    @AllArgsConstructor
    static class Person implements Runnable {

        String name;

        public void arrive() {
            shortSleep(Duration.ofMillis(r.nextInt(1000)));
            System.out.printf("%s 到达现场!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        public void eat() {
            shortSleep(Duration.ofMillis(r.nextInt(1000)));
            System.out.printf("%s 吃完!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        public void leave() {
            shortSleep(Duration.ofMillis(r.nextInt(1000)));
            System.out.printf("%s 离开!\n", name);
            phaser.arriveAndAwaitAdvance();
        }

        private void hug() {
            if (name.equals("新郎") || name.equals("新娘")) {
                shortSleep(Duration.ofMillis(r.nextInt(1000)));
                System.out.printf("%s 洞房!\n", name);
                phaser.arriveAndAwaitAdvance();
            } else {
                phaser.arriveAndDeregister();
            }
        }


        @Override
        public void run() {
            arrive();
            eat();
            leave();
            hug();
            System.out.println(name + " 结束");
        }
    }

}

ReadWriteLock

​ 读写锁

​ 读锁 -> 共享锁

​ 写锁 -> 独占锁

问题一:读为什么也要加锁?

​ 读如果不加锁的话,会造成脏读(读到别人修改过的数据)

同一把锁(读写锁),分为共享锁和独占锁两个部分。共享锁是可以共享的一种锁,当前如果共享锁被某个线程持有,其他多个尝试获取共享锁的线程都可以得到共享锁并执行。此时如果有线程想要获取独占锁,需要等全部持有共享锁的线程都释放掉。获取写锁的线程仍然可以继续获取读锁。

读锁的获取

  1. 存在写锁,并且写锁不是当前线程则直接去排队
  2. 读锁是否该阻塞,对于非公平模式下写锁获取优先级会高,如果存在要获取写锁的线程则读锁需要让步,公平模式下则先来先到
  3. CAS修改读锁状态,实际上是读锁状态+1
  4. 锁获取成功后,需要记录线程状态
  5. ThreadLocal 中获取当前线程重入读锁的次数,然后自增
  6. 进入死循环获取读锁模式

读锁的释放

  1. 清理ThreadLocal中保存的获取锁数量信息
  2. CAS修改读锁个数,实际上是自减一

写锁的获取

  1. 如果读锁数量不为0或者写锁数量不为0,并且不是重入操作,则获取失败
  2. 如果当前锁的数量为0,也就是不存在1的情况,那么该线程是有资格获取到写锁,因此修改状态,设置独占线程为当前线程

写锁的释放

  1. 如果当前线程没有获取写锁却释放,则直接抛异常
  2. 因为写锁是可以重入,所以在都释放完毕后要把独占标识清空

写锁降级策略

  1. 一个线程获取写锁之后再获取读锁,然后读锁释放掉写锁的过程

公平与非公平

  1. 公平下的Sync实现策略是所有获取的读锁或者写锁的线程都需要入队排队,按照顺序依次去尝试获取锁。
  2. 非公平下由于抢占式获取锁,写锁是可能产生饥饿,因此解决办法就是提高写锁的优先级,换句话说获取写锁之前先占坑。
public class T09_ReadWriteLock {

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

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

    public static void read(Lock lock, CountDownLatch c) {
        try {
            lock.lock();
            shortSleep(Duration.ofSeconds(1));
            System.out.println(Thread.currentThread().getName() + " read over: " + value);
            // 模拟读取操作
        } finally {
            c.countDown();
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v, CountDownLatch c) {
        try {
            lock.lock();
            shortSleep(Duration.ofSeconds(2));
            value = v;
            System.out.println(Thread.currentThread().getName() + " write over!");
            // 模拟写操作
        } finally {
            c.countDown();
            lock.unlock();
        }
    }

    public static void main(String[] args) {

        long start = System.currentTimeMillis();
        CountDownLatch c = new CountDownLatch(30);
//        Runnable readR = () -> read(lock, c);
        Runnable readR = () -> read(readLock, c);
//        Runnable writeR = () -> write(lock, new Random().nextInt(), c);
        Runnable writeR = () -> write(writeLock, new Random().nextInt(), c);

        for (int i = 0; i < 18; i++) {
            new Thread(readR, "Read Thread " + i).start();
        }

        new Thread(writeR, "Write Thread 1").start();

        for (int i = 0; i < 10; i++) {
            new Thread(readR, "Read Thread " + (i + 18)).start();
        }

        new Thread(writeR, "Write Thread 2").start();

        try {
            c.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("value: " + value);
        System.out.println("cost: " + (System.currentTimeMillis() - start));
    }
}

Semaphore

​ 信号量

​ 决定同时可允许多少个线程运行

public class T10_Semaphore {
    public static void main(String[] args) {
        Semaphore s = new Semaphore(2);
        // 允许 2 个线程同时执行

        new Thread(() -> {
            try {
                // 得不到就阻塞等待
                s.acquire();
                System.out.println("T1 running...");
                shortSleep(Duration.ofMillis(200));
                System.out.println("T1 running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }).start();

        new Thread(() -> {
            try {
                s.acquire();
                System.out.println("T2 running...");
                shortSleep(Duration.ofMillis(300));
                System.out.println("T2 running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }).start();

        new Thread(() -> {
            try {
                s.acquire();
                System.out.println("T3 running...");
                shortSleep(Duration.ofMillis(200));
                System.out.println("T3 running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }).start();

    }
}

Exchanger

​ 两个线程之间进行数据交换的一种方式

public class T11_Exchanger {
    /**
     * 线程之间交换数据的一种方式
     * 第一个调用 exchange 的线程会阻塞,等待下一个来和它交换的线程调用 exchange
     */
    private static Exchanger<String> exchanger = new Exchanger<>();

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

        new Thread(() -> {
            String s = "T2";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);
        }, "t2").start();
        
    }
}

LongAdder

​ 提供的一种基于分段锁的数据递增方式

public class T12_LongAdder {

    static AtomicLong num1 = new AtomicLong(0);
    static LongAdder num2 = new LongAdder();
    static Long num3 = 0L;

    final static Object o = new Object();

    private static final int THREAD_NUM = 1000;
    private static final int ADD_NUM = 100000;


    private static void updateByLock() throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch count = new CountDownLatch(THREAD_NUM);
        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(() -> {
                for (int j = 0; j < ADD_NUM; j++) {
                    synchronized (o) {
                        num3++;
                    }
                }
                count.countDown();
            }).start();
        }
        count.await();
        System.out.println("Lock cost: " + (System.currentTimeMillis() - start) + " num3: " + num3);
    }

    private static void updateByAtomic() throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch count = new CountDownLatch(THREAD_NUM);
        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(() -> {
                for (int j = 0; j < ADD_NUM; j++) {
                    num1.incrementAndGet();
                }
                count.countDown();
            }).start();
        }
        count.await();
        System.out.println("AtomicLong cost: " + (System.currentTimeMillis() - start) + " num1: " + num1.get());
    }

    public static void updateByLongAdder() throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch count = new CountDownLatch(THREAD_NUM);
        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(() -> {
                for (int j = 0; j < ADD_NUM; j++) {
                    num2.increment();
                }
                count.countDown();
            }).start();
        }
        count.await();
        System.out.println("LongAdder cost: " + (System.currentTimeMillis() - start) + " num2: " + num2.longValue());
    }

    public static void main(String[] args) throws InterruptedException {
        updateByLock();
        updateByAtomic();
        updateByLongAdder();
    }
}

分段锁

​ 所谓分段锁 -> 维护一个锁的数组,将1000个线程分为多个组,分别获取不同的锁。最后将所有部分的计算结果加和

ConcurrentHashMap 就是使用的分段锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值