JUC学习笔记

Posted Shinka_YXS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC学习笔记相关的知识,希望对你有一定的参考价值。

尚硅谷JUC源码讲授实战教程完整版(java juc线程精讲)

1.volatile 关键字与内存可见性

当程序运行,JVM会为每一个执行任务的线程分配独立的缓存用于提高效率。

内存可见性问题是,当多个线程操作共享数据时,彼此不可见。

volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。相较于synchronized是一种较为轻量级的同步策略。

volatile 不具备"互斥性",也不能保证变量的"原子性"

public class VolatileTest {

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(threadDemo).start();

        while (true) {
//            synchronized (threadDemo) {
                if (threadDemo.isFlag()) {
                    System.out.println("---------");
                    break;
                }
//            }
        }
    }
}

class ThreadDemo implements Runnable {

//    private boolean flag = false;
    private volatile boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (Exception e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }

    public boolean isFlag() {
        return flag;
    }
}

2.原子变量与CAS算法

原子变量:jdk1.5后java.util.concurrent.atomic包下提供了常用的原子变量(其中有AtomicInteger)
1、volatile保证内存可见性
2、CAS (Compare-And-Swap)算法保证数据的原子性CAS算法是硬件对于并发操作共享数据的支持
CAS包含了三个操作数:内存值V、预估值A、更新值B。当且仅当V == A 时,V = B(将B赋值给A)。否则,将不做任何操作

public class AtomicTest {

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

}

class AtomicDemo implements Runnable {

//    private int serialNum = 0;
    private AtomicInteger serialNum = new AtomicInteger();

    @Override
    public void run() {

        try {
            Thread.sleep(1000);
        } catch (Exception e) {
        }

        System.out.println(Thread.currentThread().getName() + ":" + getSerialNum());
    }

    public int getSerialNum() {
//        return serialNum++;
        return serialNum.getAndIncrement();
    }
}

3.模拟CAS算法

public class CompareAndSwapTest {

    public static void main(String[] args) {
        final CompareAndSwap cas = new CompareAndSwap();

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int expectedValue = cas.get();
                    boolean flag = cas.compareAndSet(expectedValue, (int)(Math.random() * 100));
                    System.out.println(flag);
                }
            }).start();
        }
    }
}

class CompareAndSwap {
    private int value;

    // 获取内存值
    public synchronized int get() {
        return value;
    }

    // 比较
    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        int oldValue = value;

        if (oldValue == expectedValue) {
            this.value = newValue;
        }

        return oldValue;
    }

    // 设置
    public synchronized boolean compareAndSet(int expectedValue, int newValue) {
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

4.同步容器类ConcurrentHashMap

  • Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。
  • ConcurrentHashMap同步容器类是Java 5增加的一个线程安全的哈希表。
    对于多线程的操作,介于HashMap 与 Hashtable之间。
    java1.8之前内部采用“锁分段”机制替代 Hashtable的独占锁。进而提高性能,1.8之后是CAS
  • 此包还提供了设计用于多线程上下文中的collection实现:
    ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和CopyOnWriteArraySet。
    当期望许多线程访问一个给定collection时,ConcurrentHashMap通常优于同步的HashMap,
    ConcurrentSkipListMap通常优于同步的TreeMap。
    当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。
/**
 * CopyOnWriteArrayList/CopyOnWriteArraySet : 写入并复制
 * 注意:不适合添加操作多的场景,因为每次添加都会进行复制,开销大,效率低
 *      适合并发迭代操作多的场景
 */
public class CopyOnWriteArrayListTest {

    public static void main(String[] args) {
        HelloThread ht = new HelloThread();

        for (int i = 0; i < 10; i++) {
            new Thread(ht).start();
        }
    }

}

class HelloThread implements Runnable {

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

    static {
        list.add("AA");
        list.add("BB");
        list.add("CC");
    }

    @Override
    public void run() {
        Iterator<String> it = list.iterator();

        while (it.hasNext()) {
            System.out.println(it.next());
            // 使用的是List<String>时 此处会抛出java.util.ConcurrentModificationException异常
            list.add("AA");
        }
    }
}

5.CountDownLatch闭锁

  • Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。

  • CountDownLatch一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

  • 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:

    确保某个计算 在其需要的所有资源都被初始化之后 才继续执行;
    确保某个服务 在其依赖的所有其他服务都已经启动之后 才启动;
    等待直到某个操作所有参与者都准备就绪再继续执行。

/**
 * CountDownLatch闭锁:在完成某些运算时,只有其他线程的运算全部完成后,当前运算才继续执行
 */
public class CountDownLatchTest {

    public static void main(String[] args) {
        // 后面开了5个线程,此处就传入计数5
        CountDownLatch countDownLatch = new CountDownLatch(5);
        LatchDemo latchDemo = new LatchDemo(countDownLatch);

        long start = System.currentTimeMillis();

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


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

        // 当计数减到0时才执行
        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:" + (end - start));
    }

}

class LatchDemo implements Runnable {
    public CountDownLatch countDownLatch;

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

    @Override
    public void run() {
        synchronized (this) {
            try {
                for (int i = 0; i < 50000; i++) {
                    if (i % 2 == 0) {
                        System.out.println(i);
                    }
                }
            }finally {
                countDownLatch.countDown();
            }
        }
    }
}

6.实现Callable接口

/**
 * 创建线程方式三:实现Callable接口
 * 相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
 */
public class CallableTest {
    public static void main(String[] args) {
        CallableImpl callableImpl = new CallableImpl();

        // 执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果。FutureTask是Future接口的实现类
        // 还有另外一个构造方法是FutureTask(Runnable runnable, V result)
        FutureTask<Integer> result = new FutureTask<>(callableImpl);

        new Thread(result).start();

        // 接收线程运算后的结果
        try {
            Integer sum = result.get(); // futureTask也可用于闭锁
            System.out.println(sum);
            System.out.println("---------当分线程执行完 get()拿到结果了才执行此处---------");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

    }
}

class CallableImpl implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}

7.Lock同步锁

—、用于解决多线程安全问题的方式:
1.同步代码块(synchronized隐式锁)
2.同步方法(synchronized隐式锁)
3.同步锁Lock(jdk 1.5后出现,是个显式锁,需要通过lock()方法上锁,必须通过unlock()方法进行释放锁,所以有一定的风险,比如锁未成功释放)

public class LockTest {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(ticket, "1号窗口").start();
        new Thread(ticket, "2号窗口").start();
        new Thread(ticket, "3号窗口").start();
    }
}

class Ticket implements Runnable {

    private int ticketNum = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();    // 上锁
            try {
                if (ticketNum > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "完成售票,余票为:" + --ticketNum);
                }
            } finally {
                lock.unlock();  // 释放锁
            }
        }
    }
}

8.生产者消费者案例-虚假唤醒

如何用Lock同步锁实现等待唤醒机制,也就是像synchronized关键字的wait()和notify()的等待唤醒机制

8.1未使用等待唤醒机制出现的问题

public class ProductorAndConsumerTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(productor, "生产者A").start();
        new Thread(consumer, "消费者B").start();
    }
}

// 店员
class Clerk {
    private int productNum = 0;

    // 进货
    public synchronized void get() {
        if (productNum >= 5) {
            System.out.println("商品仓位已满!");
//            try {
//                this.wait();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
        } else {
            System.out.println(Thread.currentThread().getName() + "进货:" + ++productNum);
//            this.notifyAll();
        }
    }

    // 卖货
    public synchronized void sale() {
        if (productNum <= 0) {
            System.out.println("商品已售罄!");
//            try {
//                this.wait();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
        } else {
            System.out.println(Thread.currentThread().getName() + "售出:" + --productNum);
//            this.notifyAll();
        }
    }
}

// 生产者
class Productor implements Runnable {

    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            clerk.get();
        }
    }
}

// 消费者
class Consumer implements Runnable {

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            clerk.sale();
        }
    }
}

即使仓位满了,生产者还是不停地进货;即使商品售罄了,消费者还是不停地想要去消费、

在这里插入图片描述

8.2使用等待唤醒机制

将8.1中注释的代码放开、此时运行结果都是有效的数据、

在这里插入图片描述

8.3问题修正1

8.2的等待唤醒机制有些问题、
当把仓位改为1,并给生产者加0.2秒的延迟、

// 进货
public synchronized void get() {
    if (productNum >= 1) {		// 把仓位改为1
        System.out.println("商品仓位已满!");
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } else {
        System.out.println(

以上是关于JUC学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

JUC基础学习笔记

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段

JAVA学习笔记25——JUC

juc并发编程学习笔记上(尚硅谷)