Java JUC

2021-01-29 14:16

阅读:464

标签:代码块   ons   之间   关键字   同步锁   asn   override   task   系统   

目录:

 

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

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

  demo:测试线程数据没有及时与主内存数据进行同步

package com.oy;

public class TestVolatile {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();

        while (true) {
            // 下面这个判断 myRunnable.isFlag() 一直是 false
            // 主线程 Thread.sleep 后,才有时间同步线程数据
            // Thread.sleep(1000);
            if (myRunnable.isFlag()) {
                System.out.println("========");
                break;
            }
        }
    }
}

class MyRunnable implements Runnable {
    private 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;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

  现象:主线程没有添加 Thread.sleep(1000); 这句代码时,主线程不会打印 "========";添加后会打印。

  原因:while(true) 效率很高,主线程没有及时将数据与主内存进行同步。共享变量保存在主内存中,子线程操作该变量时,复制一份进行操作,然后同步到主内存中。但是这个过程其他线程不可见。

技术图片

 

 

 

  解决方案一:使用 synchronized 会取同步数据

synchronized (myRunnable) {
    if (myRunnable.isFlag()) {
        System.out.println("========");
        break;
    }
}

  解决方案二:使用 volatile 关键字修饰变量

private volatile boolean flag = false;

 

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

  注意:

  • volatile 不具备“互斥性”;
  • volatile 不能保证变量的“原子性”;

 

2、原子变量与 CAS 算法

  i++ 的原子性问题:i++ 操作时实际上分为三个步骤“读-改-写”

int i = 10;
i = i++;

// 上面 i++ 操作系统底层的过程分为三步
int temp = i;
i = i + 1;
i = temp;

  

2.1、demo: 多个线程操作共享数据,参数线程安全问题

package com.oy;

public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i ) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {
    private int serialNumber = 0;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (Exception e) {
        }
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        return serialNumber++;
    }
}

  现象:

技术图片

 

 

   原因:因为变量 serialNumber 的自增 (serialNumber++) 操作不是原子性操作,当多个线程操作 serialNumber 变量时就会有线程安全问题。

 

  解决方案:

  原子变量:jdk1.5 后 java.util.concurrent.atomic 包下提供了常用的原子变量。

  • 原子变量封装的变量都使用 volatile 保证内存可见性;
  • CAS(Compare-And-Swap)算法保证数据的原子性;

 

2.2、CAS 算法

  CAS 算法时硬件对于并发操作共享数据的支持。CAS 保护三个操作数:内存值 V,预估值(旧值)A,更新值 B,当且仅当 V==A 时, V = B, 否则不做任何操作。(就是只有预估值与内存值相等时,才进行更新)

技术图片

 

 

2.3、使用 “原子变量” 改写前面的程序

  当某个线程判断预估值与内存值不等时,不进行任何处理,但是会发起重试,直至成功。

package com.oy;

import java.util.concurrent.atomic.AtomicInteger;

public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i ) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {
    //private int serialNumber = 0;
    private AtomicInteger serialNumber = new AtomicInteger();

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (Exception e) {
        }
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        //return serialNumber++;
        return serialNumber.getAndIncrement();
    }
}

 

3、同步容器类

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

  ConcurrentHashMap 同步容器类时 Java 5 增加的一个线程安全的哈希表。对于多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用 “锁分段” 机制替代 Hashtable 的独占锁,进而提高性能。Hashtable 是在其内部的每个方法上添加 synchronized 关键字进行同步,性能低,且在复合操作(比如不存在则添加、存在则删除)时,由于调用了 Hashtable 的多个方法,同样有线程安全问题。

  当使用 List list = Collections.synchronizedList()) 包装一个 ArrayList 来得到线程安全的 list 集合时同样有 “并发修改异常” 问题。

package com.oy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class TestCopyOnWriteArrayList {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        for (int i = 0; i ) {
            new Thread(mt).start();
        }
    }
}

class MyThread implements Runnable {

    //private static List list = Collections.synchronizedList(new ArrayList());
    private  static CopyOnWriteArrayList list = new CopyOnWriteArrayList();
    static {
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
    }

    @Override
    public void run() {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
            // 使用 Collections.synchronizedList 时,同样有并发修改异常
            list.add("ddd");
        }
    }
}

 

4、闭锁 CountDownLatch

    CountDownLatch 内部维护一个 count, 如果count 不为 0,latch.await() 处于等待,直至 count 为 0。

  下面案例:统计 5 个线程执行时间。每个子线程执行完后进行 latch.countDown(), 直至 count 为 0,程序latch.await()  等待的地方继续执行

package com.oy;

import java.util.concurrent.CountDownLatch;

public class TestCountDownLatch {
    public static void main(String[] args) {
        // 创建闭锁对象
        int threadNum = 5;
        CountDownLatch latch = new CountDownLatch(threadNum);
        LatchDemo ld = new LatchDemo(latch);

        // 计算下面多个线程的执行时间
        long start = System.currentTimeMillis();
        for (int i = 0; i ) {
            new Thread(ld).start();
        }
        // latch.await(): 等待锁 countDown
        try {
            latch.await();
        } catch (Exception e) {
        }
        long end = System.currentTimeMillis();
        System.out.println("执行时间:" + (end - start) + " ms");

    }
}

class LatchDemo implements Runnable {
    private CountDownLatch latch;

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

    @Override
    public void run() {

        synchronized (this) {
            try {
                int sum = 0;
                for (int i = 1; i ) {
                    sum += i;
                }
                System.out.println(Thread.currentThread().getName() + ", sum=" + sum);
            } finally {
                latch.countDown();
            }
        }

    }
}

 

5、使用 Callable 创建线程

package com.oy;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class TestCallable {
    public static void main(String[] args) throws Exception {
        Demo d = new Demo();
        // 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收结果
        FutureTask result = new FutureTask(d);
        new Thread(result).start();

        // 接收线程运算后的结果
        // 线程没有返回结果前,result.get() 处于等待
        Integer sum = result.get(); // FutureTask 可用于闭锁
        System.out.println(Thread.currentThread().getName() + ", sum=" + sum);

    }
}

class Demo implements Callable {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i ) {
            sum += i;
        }
        System.out.println(Thread.currentThread().getName() + ", sum=" + sum);
        return sum;
    }
}

 

6、Lock 同步锁

  解决多线程安全问题的方式

  • synchronized(隐式锁) 同步代码块
  • synchronized(隐式锁) 同步方法
  • jdk 1.5 后的同步锁 Lock(显示锁,需要通过 lock()方法上锁,通过 unlock() 方法进行释放锁)
package com.oy;


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

public class TestLock {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        new Thread(t, "1 号窗口").start();
        new Thread(t, "2 号窗口").start();
        new Thread(t, "3 号窗口").start();
    }
}

class Ticket implements Runnable {
    private int tick = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock(); // 加锁
                if (tick > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                    }

                    System.out.println(Thread.currentThread().getName() + " 完成售票,余票为:" + (--tick));
                }
            } finally {
                lock.unlock(); // 释放锁
            }

        }
    }
}

 

7、

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

---

Java JUC

标签:代码块   ons   之间   关键字   同步锁   asn   override   task   系统   

原文地址:https://www.cnblogs.com/xy-ouyang/p/12832077.html


评论


亲,登录后才可以留言!