Java多线程高并发之详解JUC同步工具

1. ReentrantLock可重入锁(锁同一个对象才有可重入的概念)
通过以下代码段来理解:

public class TestReentrantLock {
	Lock lock = new ReentrantLock();
	void m1() {
		try {
			lock.lock(); //synchronized(this)
			for (int i = 0; i < 10; i++) {
				TimeUnit.SECONDS.sleep(1);
				System.out.println(i);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

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

	public static void main(String[] args) {
		TestReentrantLock rl = new TestReentrantLock();
		new Thread(rl::m1).start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(rl::m2).start();
	}
}

reentrantlock用于替代synchronized, 由于m1锁定this,只有m1执行完毕的时候,m2才能执行,这里是复习synchronized最原始的语义, 使用reentrantlock可以完成同样的功能, 需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍), 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放。

ReentrantLock 与synchronized的区别

  • synchronized中的锁是非公平锁,ReentrantLock默认也是非公平锁,但是通过布尔类型参数的构造函数来指定实现公平锁
  • ReentrantLock使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行;可以根据tryLock的返回值来判定是否锁定;也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中
    lock.lockInterruptibly(); //可以对interrupt()方法做出响应,在一个线程等待锁的过程中,可以被打断

ReentrantLock还可以指定为公平锁
多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁;而非公平锁则不能保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁
ReentrantLock lock=new ReentrantLock(true); //参数为true表示为公平锁,请对比输出结果

public class T05_ReentrantLock5 extends Thread {		
	private static ReentrantLock lock=new ReentrantLock(true); //参数为true表示为公平锁,请对比输出结果
    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) {
        T05_ReentrantLock5 rl=new T05_ReentrantLock5();
        Thread th1=new Thread(rl);
        Thread th2=new Thread(rl);
        th1.start();
        th2.start();
    }
}

2. CountDownLatch
CountDownLatch的作用:等待所有线程结束,比用join灵活。

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;
            latch.countDown();//线程执行完-1
        });
    }
    for (int i = 0; i < threads.length; i++) {
        threads[i].start();
    }
    try {
        latch.await();//等待所有线程结束,countDown到0时所有线程结束放行。
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("end latch");
}

CountDownLatch使用场景
电商的详情页,由众多的数据拼装组成,如可以分成一下几个模块
交易的收发货地址,销量
商品的基本信息(标题,图文详情之类的)
推荐的商品列表
评价的内容

  • 上面的几个模块信息,都是从不同的服务获取信息,且彼此没啥关联;所以为了提高响应,完全可以做成并发获取数据,如 :
    线程1获取交易相关数据
    线程2获取商品基本信息
    线程3获取推荐的信息
    线程4获取评价信息
  • 但是最终拼装数据并返回给前端,需要等到上面的所有信息都获取完毕之后,才能返回,这个场景就非常的适合 CountDownLatch来做了
    在拼装完整数据的线程中调用 CountDownLatch#await(long, TimeUnit) 等待所有的模块信息返回
    每个模块信息的获取,由一个独立的线程执行;执行完毕之后调用 CountDownLatch#countDown() 进行计数-1

3. CyclicBarrier栅栏
循环栅栏,等到多少个线程为止够了才执行一次操作
使用场景:一个复杂的场景,需要同时执行数据库,访问网络,文件时 分批循环执行

public class T07_TestCyclicBarrier {
    public static void main(String[] args) {
        //CyclicBarrier barrier = new CyclicBarrier(20);
        //CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));
        CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
            @Override
            public void run() {
                System.out.println("满人,发车");
            }
        });
        for(int i=0; i<100; i++) {
                new Thread(()->{
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();            
        }
    }
}

4. Phaser阶段
将一个连续的执行过程分成固定阶段,可以控制栅栏的个数和等待线程的个数;婚礼流程案例:到场,吃饭,离开,洞房。

public class T09_TestPhaser2 {
    static Random r = new Random();
    static MarriagePhaser phaser = new MarriagePhaser();
    static void milliSleep(int milli) {
        try {
            TimeUnit.MILLISECONDS.sleep(milli);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    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);
                    System.out.println();
                    return false;
                case 1:
                    System.out.println("所有人吃完了!" + registeredParties);
                    System.out.println();
                    return false;
                case 2:
                    System.out.println("所有人离开了!" + registeredParties);
                    System.out.println();
                    return false;
                case 3:
                    System.out.println("婚礼结束!新郎新娘抱抱!" + registeredParties);
                    return true;
                default:
                    return true;
            }
        }
    }
    static class Person implements Runnable {
        String name;
        public Person(String name) {
            this.name = name;
        }
        public void arrive() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 到达现场!\n", name);
            phaser.arriveAndAwaitAdvance();
        }
        public void eat() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 吃完!\n", name);
            phaser.arriveAndAwaitAdvance();
        }
        public void leave() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 离开!\n", name);
            phaser.arriveAndAwaitAdvance();
        }
        private void hug() {
            if (name.equals("新郎") || name.equals("新娘")) {
                milliSleep(r.nextInt(1000));
                System.out.printf("%s 洞房!\n", name);
                phaser.arriveAndAwaitAdvance();
            } else {
                phaser.arriveAndDeregister();//其它线程减一
                //phaser.register()
            }
        }
        @Override
        public void run() {
            arrive();
            eat();
            leave();
            hug();
        }
    }
}

5. Lock:ReadWriteLock
读写锁:读写分开执行,提高性能。

public class T10_TestReadWriteLock {
    static Lock lock = new ReentrantLock();//同步锁 非公平锁
        private static int value;
    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//读写分开执行,提高性能
       static Lock readLock = readWriteLock.readLock();//共享锁,多个线程同时读
    static Lock writeLock = readWriteLock.writeLock();//排它锁,只能一个线程写
    public static void read(Lock lock) {
        try {
            lock.lock();//加锁
            Thread.sleep(1000);
            System.out.println("read over!");
            //模拟读取操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //模拟写操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        //Runnable readR = ()-> read(lock);
        Runnable readR = () -> read(readLock);
        //Runnable writeR = ()->write(lock, new Random().nextInt());
        Runnable writeR = () -> write(writeLock, new Random().nextInt());
        for (int i = 0; i < 18; i++) new Thread(readR).start();
        for (int i = 0; i < 2; i++) new Thread(writeR).start();
    }
}

延申:分布式锁,秒杀系统
从数据库中读电视数据500台,最多秒杀500台;如何完成秒杀?
单机:前面的线程访问同一个数开始为0直到涨到500,AtomicInteger实现
分布式:采用分布式锁可采用redis

6. Semphore信号灯
信号灯作用:限流,允许多个线程同时执行,场景:卖票只能允许5个窗口同时卖票,高速收费站

public class T11_TestSemaphore {
    public static void main(String[] args) {
        //Semaphore s = new Semaphore(2);//数字为2 代表允许两个线程同时执行
        Semaphore s = new Semaphore(2, true);//第二个参数设置是否公平,默认为false非公平
        //允许一个线程同时执行
        //Semaphore s = new Semaphore(1);
        new Thread(() -> {
            try {
                s.acquire();//得到取得
                System.out.println("T1 running...");
                Thread.sleep(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...");
                Thread.sleep(200);
                System.out.println("T2 running...");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

7. Exchanger交换器
线程之间交换数据用,只能允许两个线程之间交换,第三个出现时,第三个线程一直等待第四个线程出现
场景:双人游戏中两人交换装备

public class T12_TestExchanger {
    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();
    }
}

8. LockSupport线程阻塞工具类
所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。
LockSupport的park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用。
park不需要获取某个对象的锁,park和unpark的使用不会出现死锁的情况
因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理
blocker的作用是在dump线程的时候看到阻塞对象的信息

public class LockSupportDemo {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super(name);
        }
        @Override public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("被中断了");
                }
                System.out.println("继续执行");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(1000L);
        t2.start();
        Thread.sleep(3000L);
        t1.interrupt();
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

运行的结果如下:
in t1
被中断了
in t2
继续执行

9. 锁的分类各种的特点,以及它们在java中的具体实现

  • 乐观锁:
    每次拿数据时先不会上锁,但是在更新的时候会判断在此期间别人是否更新过数据,可以使用CAS算法或者版本号机制实现;应用与多读的场景提高吞吐量;
    具体实现:数据库中的write_condition,java中的AotimcXXX原子类型变量
  • 悲观锁:每次拿数据都会上锁;
    具体实现:数据库中的行锁,表锁,读锁写锁都是在操作之前先上锁;Java中synchronized,ReentrantLock等独占锁
  • 自旋锁:
    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
    具体实现:AtomicInteger
  • 读写锁(排它锁,共享锁) ReadWriteLock
    ReadWriteLock其读锁是共享锁,其写锁是独享锁;读锁的共享锁可保证并发读是非常高效的读写,写读 ,写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 对于Synchronized而言,当然是独享锁。
  • 分段锁
    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
    我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
    但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。 分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值