java多线程(3):JUC基础概述

几个基本概念

volatile,不保证原子性,只保证可见性,也就说不能保证线程安全。
如果是一写多读,那么jvm可以解决同步问题。但是,如果是多写,则线程并非安全。
实现原理是禁止指令重排序,并强制线程从主存储器获取数据。具体实现原理是在该数据发生更改时,线程会立刻将其写回主存。其他cpu都在嗅探主存储器写总线,当发现自己持有的缓存被修改时,对自己的缓存做无效处理,在下次访问时直接从主存储器中读取。

CAS操作
CAS有三个关键字,内存值V,旧的预期值A,和要修改的新值B,只有A=V时,则将V修改为B,否则什么都不做。

CAS通过调用JNI代码实现。CAS是x86,x64 CPU提供的元语(ARM最新版也添加了支持,以前都是用其他元语模拟的)。在最老的处理器上,采用的是锁住内存总线的方式,直接阻止其他cpu访问内存。

CAS操作会有ABA问题。
线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题。各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference<E>也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题,例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新,AtomicInteger会成功执行CAS操作,而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败

详情请参考这篇博文
https://www.cnblogs.com/java20130722/p/3206742.html

JUC原子类

使用java.util.concurrent.atomic可以在不用synchronized和lock情况下对变量进行原子性操作。

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

public class SimpleTest implements Runnable {
    // 创建AtomicInteger,初始值0(也可以指定初始值)
//    private static final AtomicInteger nextSerialNum = new AtomicInteger();
    private static final AtomicLong nextSerialNum = new AtomicLong();

    @Override
    public void run() {
        
        // 直接取得当前值并增长1
        System.out
                .println(Thread.currentThread().getName() + ":" + nextSerialNum.getAndIncrement());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }

    public static void main(String[] args) {
        SimpleTest st = new SimpleTest();

        for (int i = 0; i < 100; i++) {
            new Thread(st, "Thread" + i).start();
        }
    }
}

JUC中的集合

juc中的集合包含CopyOnWriteArrayList(实现list接口)和CopyOnWriteArraySet(实现set接口)。前者相当于线程安全的Arraylist,后者采用前者实现,相当于线程安全的HashSet。ConcurrentSkipListSet实现了set接口,相当于一个有序的treeset

ConcurrentHashMap,ConcurrentSkipListMap都实现了map接口。ConcurrentHashMap相当于线程安全的hashmap,而ConcurrentSkipListMap则是一个有序的哈希表,相当于一个线程安全的treemap。

实现queue接口的有以下几个类

  • ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。
  • LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。
  • LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。
  • ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。
  • ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。

##JUC中的lock
Lock接口
使用示例如下

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

public class SellTicket implements Runnable {

    // 定义票
    private int tickets = 100;

    // 定义锁对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 加锁
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票");
                }
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    }

}

ReadWriteLock
定义了一些读者可以共享而写入着可以独占的锁。只有一个实现,就是ReentrantReadWriteLock。

##AQS(AbstractQueuedSynchronizer,队列同步器)
ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier和 Semaphore等这些类都是继承AQS类实现的。独占锁和共享锁的父类都是这个。这个类使用了模版方法,在这个类中只定义算法的骨架,而将一些步骤的实现延迟到子类中。
acquire方法会调用子类实现的tryAcquire尝试获取同步状态,如果失败,将线程作为node节点添加到同步队列(CLH)的队尾。这个队列的头部节点就是目前正在持有锁的节点。头部节点的对应线程在释放锁时,负责唤醒下一个节点。依赖LockSupport实现。

LockSupport

JUC的基础,底层依赖unsafe实现。

FUTURETASK

java可以通过继承Thread类或者实现Runnable接口来实现多线程,但是缺点是无法获得线程执行的结果。JUC为我们提供了这个FUTURETASK。类似runable接口中的实现run方法,我们可以实现callable接口中的call方法。这样,就可以让我们在线程结束后通过Future类实例获取线程的运行结果。

public class CallableAndFuture {
    public static void main(String[] args) {
        Callable<Integer> callable = new Callable<Integer>() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        };
        FutureTask<Integer> future = new FutureTask<Integer>(callable);
        new Thread(future).start();
        try {
            Thread.sleep(5000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

JUC的类图

《java多线程(3):JUC基础概述》

参考:
https://www.cnblogs.com/nayitian/p/3264028.html

    原文作者:JUC
    原文地址: https://blog.csdn.net/define_us/article/details/78523893
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞