JUC 基本操作

一、JUC-TimeUnit枚举

TimeUnit是  java.util.concurrent 中的一个枚举类(时间单元类)。一般让进行控制线程睡眠时使用。

TimeUnit提供了可读性更好的线程暂停操作,通常用来替换Thread.sleep(),相比 Thread.sleep()方法的一个好处就是 TimeUnit可以设置时间单位。

这个类支持有:日(DAYS)、时(HOURS)、分(MINUTS)、秒(SECONDS)、毫秒(MILLISECONDS)、微秒(MICROSECONDS)、纳秒(NANOSECONDS)。

    

1、实例:进行休眠控制,休眠2秒

    使用Thread.sleep() 方法处理

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("[sleep start]"+System.currentTimeMillis());
                try {
                    Thread.sleep(2*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("[sleep end]"+System.currentTimeMillis());
            }
        }).start();
    }

    直接使用TimeUnit类来处理

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("[sleep start]"+System.currentTimeMillis());
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("[sleep end]"+System.currentTimeMillis());
            }
        }).start();
    }

2、时间单位的转换

在 TimeUnit类中最为重要的特点是可以方便的进行各种时间单位的转换,它提供了一个convert()方法

实例:1小时转换为秒数

    public static void main(String[] args) {
        long sec = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS);
        System.out.println("1小时转换为秒数:" + sec);

        long minutes = TimeUnit.MINUTES.convert(sec, TimeUnit.SECONDS);
        System.out.println(sec + "秒有转换为分钟:" + minutes);
    }

二、JUC-Atomic Variables(原子变量)

 简单使用:volatile关键字与内存可见性

三、JUC-Concurrent Collections(并发容器)

JUC里的同步容器类

1、java 基础数据集合容器中

线程安全与非线程安全的对象如下

Collection  |--List |--ArrayList
            |       |--LinkList
            |       |--Vector -->线程安全
            |
            |
            | --Set |--TreeSet
                    |--HashSet
Map |--TreeMap
    |--HashMap
    |--HashTable -->线程安全

StringBuffer  -->线程安全
StringBulider -->非线程安全

解决这些非线程安全的集合的线程安全

通过使用的 Collections.synchronizedXXX()方法来转换。HashMap的话可以直接使用HashTable转换

    

(1)List用线程安全对象处理,map同理

public class CollectionDemo {

    private static Vector list = new Vector();
//    private static List<Integer> list = new ArrayList();
//    private static List<Integer> list = Collections.synchronizedList(new ArrayList<>());

    public static void main(String[] args){
        //让50个线程去执行
        for (int i = 0; i < 50; i++) {
            new MyThread().start();
        }
    }

    private static class MyThread extends Thread{
        @Override
        public void run() {            
            for (int i = 0; i < 100; i++) {
                list.add(i);
            }
            System.out.println("集合大小:--->"+list.size());//预期的集合大小应该是50x100=5000
        }
    }

}

(2)读写处理

public class CollectionDemo {

    private static List<Object> list = Collections.synchronizedList(new ArrayList<>());

    public static void main(String[] args){
        for (int i = 0; i < 10; i++) {
            new MyThread().start();
        }
    }

    private static class MyThread extends Thread{
        @Override
        public void run() {
            Iterator<Object> iterator = list.iterator();
            while (iterator.hasNext()){
                System.out.println(iterator.next());
            }
            list.add("add" + Thread.currentThread().getName());
        }
    }

}

    

注意:上面的解决方法,一是程序效率低;二是在复合操作的时候会报并发修改异常(ConcurrentModificationException)。
 

2、JUC 解决非线程安全的集合类

JUC里面提供了一系列同步容器类用来解决非线程安全的集合类,我们只需要在多线程并发编程中,用这些同步容器类类替换掉原来的HashMap,ArrayList,HashSet集合,就可以了保证即是线程安全的,比使用 Collections.synchronizedXXX()的效率高。

HashMap -- ConcurrentHashMap

TreeMap -- ConcurrentSkipListMap

ArrayList -- CopyOnWriteArrayList

ArraySet -- CopyOnWriteArraySet等等

CopyOnWriteArrayList和CopyOnWriteArraySet 每次存入要新建一个存储结构,写操作效率低比Vector都低,浪费空间,用于写得少,读得多。

public class CollectionDemo {

    private static CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args){
        for (int i = 0; i < 5; i++) {
            new MyThread().start();
        }
    }

    private static class MyThread extends Thread{
        @Override
        public void run() {
            Iterator<Object> iterator = list.iterator();
            while (iterator.hasNext()){
                System.out.println(Thread.currentThread().getName() + "==" + iterator.next());
            }
            list.add("add" + Thread.currentThread().getName());
        }
    }

}

    

四、JUC-Synchronizers(同步器)

JUC-Synchronizers(同步器),有三个类,用于同步一批线程的行为,分别是:

  • CountDownLatch
  • Semaphore
  • CyclicBarrier

1、CountDownLatch类 - 倒计时锁

CountDownLatch是一个倒计时锁/计数器闭锁(俗称发令枪),它允许一个或多个线程等待倒计时为0的时候才执行。

所谓的倒计时:其实就是一个 int类型的变量,在初始化 CountDownLatch的时候会给他一个初始值(程序员定的);在多线程工作的时候可以通过countDown()方法来对计数器-1(计数器的操作是原子操作);当等于0的时候线程则会解阻塞运行。

例如:为了让主线程等待工作线程执行完成,主线程调用await操作让主线程阻塞,当工作线程完成初始化过程之后,每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

public class CountDownLatchDemo {

    public static void main(String[] args) {
        // 创建 CountDownLatch, 3个线程任务
        CountDownLatch countDownLatch = new CountDownLatch(3);
        LatchDemo latchDemo = new LatchDemo(countDownLatch);
        for (int i = 0; i < 3; i++) {
            new Thread(latchDemo).start();
        }

        try {
            // 主线程执行await方法,进行等待,直到计数器的值为0时继续向下执行
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程运行完成");

    }

    public static class LatchDemo implements Runnable{
        private CountDownLatch countDownLatch = null;

        public LatchDemo(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                for (int i = 0; i <5; i++) {
                    System.out.println(Thread.currentThread().getName() + "==" + i);
                }
            } finally {
                // 计数器减1
                countDownLatch.countDown();
            }
        }
    }
}

 

2、Semaphore类 - 计数信号量

Semaphore类是一个控制访问多个共享资源的计数信号量,它相当于控制使用公共资源的活动线程的数量。
即用来限制对资源访问线程数的上限。

在信号量上的两种操作:

  • acquire(获取): 当一个线程调用 acquire操作时,成功获取信号量,则信号量减1,如果没有可用信号量就等待。
  • release(释放):释放信号量,即信号量加1,然后唤醒等待的线程。

如果 acquire 之后抛异常,信号量不会自动归还,所以,尽量放到 finally 块中, 防止信号量流失。

例如:一个停车场有5个车位,假设有10台车进来停车,一个车位同时只能被一台车使用,只有车使用完开走了,其他车才能继续使用。

    public static void main(String[] args) {
        // //每次访问的线程上限是5(5个车位)
        Semaphore semaphore = new Semaphore(5);

        for (int i = 1; i <= 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "抢到车位了");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println(Thread.currentThread().getName() + "离开了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        semaphore.release();
                    }
                }
            }).start();
        }
    }

 

3、CyclicBarrier类 - 重复栅栏

CyclicBarrier 的字面意思是回环栅栏/可循环使用栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。

它允许一组线程到达某个公共屏障点 (common barrier point)时被阻塞,直到最后一个线程到达屏障(同步点)时,屏障才会开门,所有被屏障拦截的线程都到齐了才会继续干活。
因为该 barrier 在释放等待线程后可以重用,所以称它为重复栅栏/循环栅栏。

    

实例:周末5人组织大巴去旅游,总共有两个景点,每个景点约定好游玩时间,一个景点结束后需要集中一起出发到下一个景点。

public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable(){
            // 当所有线程到达barrier时执行
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "最后一个到达,人齐了!");
            }
        });

        BarrierDemo barrierDemo = new BarrierDemo(cyclicBarrier);
        for (int i = 0; i < 5; i++) {
            new Thread(barrierDemo).start();
        }


    }

    public static class BarrierDemo implements Runnable {
        private CyclicBarrier cyclicBarrier = null;

        public BarrierDemo(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "到达景点1");
                cyclicBarrier.await();// 线程在这里等待,直到所有线程都到达barrier。
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "到达景点2" );
                cyclicBarrier.await();// 线程在这里等待,直到所有线程都到达barrier。
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            }
        }
    }
}

    

五、JUC- Exchanger(线程之间数据交换)

Exchanger用于进行线程间的数据交换,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据

  • 两个线程通过Exchanger.exchange(obj)方法交换数据,如果一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange方法
  • 当两个线程都达到同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方(只能在两个线程之间交换数据)
public class ExchangerDemo {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String a = "A数据";
                    String exchangeData = exchanger.exchange(a); //交换我自己的数据,并且获取别人的数据
                    System.out.println("线程a:" + exchangeData);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String b = "b数据";
                    String exchangeData = exchanger.exchange(b); //交换我自己的数据,并且获取别人的数据
                    System.out.println("线程b:" + exchangeData);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

    

参考文章: JUC回顾之-AQS同步器的实现原理:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值