0.JUC的简介
- 在Java5.0中提供了java.util.concurrent(简称JUC)包,在此包中增加了很多在并发编程中很实用的类,用于定义类似线程的自定义子系统,包括线程池,异步IO,轻量级任务框架。提供可调的,灵活的线程池。还提供了设计用于多线程上下文中的Collection实现等。
1.volatile关键字-内存可见性
1.1 volatile关键字
- 保证内存可见性
- 禁止内存指令重排
- 不保证原子性(线程操作时没有互斥性,可以多个线程同时操作)
1.2 volatile关键字的demo
/**
*
* 本测试用来测试votalile关键字的内存可见性
* @author
* @version 1.00
* @time 2020/3/12 0012 下午 3:56
*/
public class VolatileTest {
public static void main(String[] args) {
//创建myThread对象,启动线程
MyThread myThread = new MyThread();
new Thread(myThread).start();
while (true){
//如果为flase则中断
if(myThread.getFlag()){
System.out.println("主线程循环中断=======");
break;
}
}
}
}
//定义MyThread线程类
class MyThread implements Runnable{
//定义全局变量flag,设置初始值为false.并实现get,set方法
private Boolean flag = false;
//实现run方法
@Override
public void run() {
//线程睡0.3秒后将flag改为true
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag="+flag);
}
public Boolean getFlag() {
return flag;
}
public void setFlag(Boolean flag) {
this.flag = flag;
}
}
控制台输出:
flag=true main方法的while循环没中断,说明main方法的线程么有获取到flag变量已变为true。
解释:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htCMRdMu-1584702325287)(.\jucimages\volatile缓存.jpg)]
上面图片可以看着一个简单的jvm内存模型,在主存存储flag的值为false,其中的flag变量的内存变化如下:
- 自定义线程先从主存中读取flag的值到自己的缓存中,读取完成后将值改为true后,在将值写回主存中,写回后打印,flag == true。
- 在自定义线程未修改flag值前,main线程读取flag的值为flase,然后开始执行while循环,因为while循环执行效率特别高,main线程没有空闲去重新读取主存中的flag值,一直使用自己缓存中的值,这样造成内存不可见。
1.3 volatile的内存可见性
内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。注意,一个读一个写操作时出现。
。我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的 volatile 变量。
demo:
/**
*
* 本测试用来测试votalile关键字的内存可见性 共享变量加volatile解决内存可见性问题
* @author
* @version 1.00
* @time 2020/3/12 0012 下午 3:56
*/
public class VolatileTest2 {
public static void main(String[] args) {
//创建myThread对象,启动线程
MyThread1 myThread1 = new MyThread1();
new Thread(myThread1).start();
while (true){
//如果为flase则中断
if(myThread1.getFlag()){
System.out.println("主线程循环中断=======");
break;
}
}
}
}
控制台输出:
主线程循环中断=======
flag=true
测试完成,,添加volatile关键字解决了共享数据的可见性问题。内存可见性问题出现在两个线程一个读一个写的情况下,如果两个都是写操作就要考虑原子性。
1.4 volatile关键字的特性
- 保证内存可见性
- 禁止指令重排
- 不保证原子性
2.原子变量与CAS算法
2.1 原子性和原子变量
2.1.1 原子性
原子性是指操作上不可分割,就是一个操作无论有几步都要同时执行完成,不可分割。
-
常见的i++操作就不具备原子性。
- i = 10 ;i = i++;此时打印i,i的值为10,此时i++操作分为三步,
- 1,将i的值赋值给临时变量 int tem = 10;
- 2,i = i + 1;
- 3, 将tmp的值赋值给i, i = tmp,此时i的值为10,这就是i++的底层操作,分为3步。
-
下面用例子测试不用原子类和用原子变量类进行多线程的i++操作。
public class AtomicClassTest {
public static void main(String[] args) {
MyThreadDemo myThreadDemo = new MyThreadDemo();
for (int i = 0; i <= 10; i++) {
//创建10个线程,共享MyThreadDemo
new Thread(myThreadDemo).start();
}
}
}
class MyThreadDemo implements Runnable {
private int i = 0;
//实现run方法
@Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用i++并打印值
System.out.println("此时i的值为" + iPlusPlust());
}
//i++fangf
public int iPlusPlust() {
return i++;
}
}
输出为:
此时i的值为0
此时i的值为7
此时i的值为6
此时i的值为4
此时i的值为5
此时i的值为1
此时i的值为3
此时i的值为2
此时i的值为0
此时i的值为1
此时i的值为8
此时i为1出现两次,出现线程安全问题。
2.1.2 原子变量
-
在java.util.current.atomic包下面有常用的原子类,原子类的操作具有原子性
-
demo
/** * atomic原子变量类的测试 * @author * @version 1.00 * @time 2020/3/13 0013 下午 12:09 */ public class AtomicClassTest2 { public static void main(String[] args) { MyThreadDemo1 myThreadDemo = new MyThreadDemo1(); for (int i = 0; i <= 10; i++) { //创建10个线程,共享MyThreadDemo new Thread(myThreadDemo).start(); } } } class MyThreadDemo1 implements Runnable { private AtomicInteger ato = new AtomicInteger(); //实现run方法 @Override public void run() { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } //调用i++并打印值 System.out.println("返回的结果值为:" + iPlusPlus()); } //i++fangf public int iPlusPlus() { return ato.getAndIncrement(); } } 运行结果为: 返回的结果值为:0 返回的结果值为:2 返回的结果值为:1 返回的结果值为:3 返回的结果值为:4 返回的结果值为:5 返回的结果值为:6 返回的结果值为:10 返回的结果值为:8 返回的结果值为:9 返回的结果值为:7 解决了i++操作的原子性问题,,即线程安全问题。2.1.3 原子变量的简单原理2.1.3 原子变量简单原理
2.1.3 原子变量类简单原理
- 原子变量类内部变量用volatily关键字进行修饰,保证内存可见性。
- 原子变量类内部采用了CAS算法,(compare-and-swap)比较和替换,CAS 是一种无锁的非阻塞算法的实现.
- 第一步,读取要更改的变量的值;
- 第二步,将读取的值和预估值(也称为旧值)进行比较;(二,三步同时进行)
- 第三步,如果读取值和预估值一致则特换为新值,如果不一致则不替换(不一致则说明数值已经被更改了)。
- cas算法效率比锁高的原因是:cas算法的线程和线程等待不同,cas算法不会失去cpu的占用权,效率高。
3.ConcurrentHashMap锁分段机制
3.1 HashTable的线程安全问题
-
HashTable是一个Java.util包为我们提供的一个线程安全的map。但是它在进行复合操作时任然有线程安全问题,例如:
HashTable ht = new HashTable(); //如果不存在则添加 if(ht.contants("a")){ ht.add("a"); } //因为HashTable的各个方法是用Synchronized修饰的,每个方法各自加锁,线程1判断完包含后,向map里面添加数据前,线程二往map里面添加了同样的数据,这样判断已经不成立了,出现多线程操作问题。
3.2 ConcurrentHashMap
- ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对于多线程的操作,介于HashMap 与 Hashtable 之间。内部采用“锁分段”机制替代 Hashtable 的独占锁。进而提高性能。
- java.util.concurrent包还提供了设计用于多线程上下文中的 Collection 实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。
3.3 CopyOnWriteArrayList
-
在Java中可以使用Collections.synchronizedList(new ArrayList )方法将一个ArrayList包装成线程安全的List。
但是这个List在进行迭代时任然是不安全的。因为如果看SynchronizedList源码可以看出执行add()等方法的时候是加了synchronized关键字的,但是listIterator(),iterator()却没有加.所以在使用的时候需要加synchronized.
-
此时可以同过CopyOnWriteArrayList进行替换,解决线程安全问题
public class ConcurrentHashMapTest { public static void main(String[] args) { MyCurrentDemo myCurrentDemo = new MyCurrentDemo(); for(int i=0; i<=10; i++){ new Thread(myCurrentDemo).start(); } } } class MyCurrentDemo implements Runnable { //定义全局变量 private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); //初始化list static { list.add("A"); list.add("B"); list.add("C"); list.add("D"); list.add("E"); } @Override public void run() { //边遍历边添加 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); //往list里面添加元素,不是iterator里面 list.add("AAA"); } } } 打印正常,如果将CopyOnWriteArrayList改为Collections.synchronizedList(new ArrayList)则报java.util.ConcurrentModificationException异常。
4.CountDownLatch
-
CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
-
闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行;
- 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
- 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
- 等待直到某个操作所有参与者都准备就绪再继续执行。
public class CountDownLatchTest2 { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(10); MyDemo1 myDemo = new MyDemo1(countDownLatch); long start = System.currentTimeMillis(); //开启5个线程 for(int i=0; i<10; i++){ new Thread(myDemo).start(); } //进行等待,线程运行结束后才释放 try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("运行的时间:"+(end - start)); } } /** * * 线程类 打印0-5000以内的整数 */ class MyDemo1 implements Runnable{ private CountDownLatch latch; public MyDemo1(CountDownLatch latch) { this.latch = latch; } @Override public void run() { //加锁 防止线程安全问题 synchronized (this){ try { for (int i=0; i<=5000; i++){ if(i%2 == 0){ System.out.println(Thread.currentThread().getName()+"打印数字"+i); } } } finally { //latch必须减一,所以放在finally中 latch.countDown(); } } } }
5.0 生产者消费者模式
5.1使用synchronized关键字和wait(),notifyAll()方法实现
/**
*
* 生产者 消费者模式
* @author
* @version 1.00
* @time 2020/3/14 0014 下午 6:58
*/
public class CustomerTest {
public static void main(String[] args) {
Store store = new Store();
new Thread(new customer(store),"消费者A").start();
new Thread(new productor(store), "生产者B").start();
}
}
/**
* 生产者 和 消费者 之间的桥梁。存储商品最多为10个
*/
class Store{
private Integer store = 0;
//生产商品 同步方法
public synchronized void producter(){
while (store >= 10){
System.out.println(Thread.currentThread().getName()+"商品库存已满");
//超过10个停止生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//继续生产
System.out.println(Thread.currentThread().getName()+"生产商品,此时商品为:"+ ++store);
//唤醒其他线程
this.notifyAll();
}
//消费商品 同步方法
public synchronized void customer(){
while (store <= 0){
System.out.println(Thread.currentThread().getName()+"此时商品库存为0");
//库存少于0个停止消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进行消费
System.out.println(Thread.currentThread().getName() +"消费商品,库存为:"+ --store);
this.notifyAll();;
}
}
/**
* 消费者
*/
class customer implements Runnable{
private Store store;
//构造器
public customer(Store store) {
this.store = store;
}
@Override
public void run() {
//调用生产方法,生成二十个
for(int i=0; i<=20; i++){
store.customer();
}
}
}
/**
* 生产者
*/
class productor implements Runnable{
private Store store;
//构造器
public productor(Store store) {
this.store = store;
}
@Override
public void run() {
for(int i=0; i<=20; i++){
store.producter();
}
}
}
- 注意的是,,在Store类中的消费和生产方法的条件判断都是While判断,此处不可以使用if判断,如果if判断会造成虚假唤醒!!
5.2 使用Condition和ReentrantLock实现,生产者消费者模式
5.2.1 ReentrantLock
- 在 Java 5.0 之前,协调共享对象的访问时可以使用的机制只有 synchronized 和 volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
- ReentrantLock 实现了 Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。
5.2.2 Condition
- Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
- 在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll。
- Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用newCondition() 方法。
/**
* 生产者 消费者模式 使用ReentrantLock和Condition实现
*
* @author
* @version 1.00
* @time 2020/3/14 0014 下午 6:58
*/
public class CustomerTest2 {
public static void main(String[] args) {
Store1 store = new Store1();
new Thread(new Customer1(store), "消费者A").start();
new Thread(new Productor1(store), "生产者B").start();
}
}
/**
* 生产者 和 消费者 之间的桥梁。存储商品最多为10个
*/
class Store1 {
private Integer store = 0;
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//生产商品 同步方法
public void producter() {
lock.lock();
try {
while (store >= 10) {
System.out.println(Thread.currentThread().getName() + "商品库存已满");
//超过10个停止生产
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//继续生产
System.out.println(Thread.currentThread().getName() + "生产商品,此时商品为:" + ++store);
//唤醒其他线程
condition.signalAll();
} finally {
//解锁
lock.unlock();
}
}
//消费商品 同步方法
public void customer() {
lock.lock();
try {
while (store <= 0) {
System.out.println(Thread.currentThread().getName() + "商品库存缺货");
//库存少于0个停止消费
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进行消费
System.out.println(Thread.currentThread().getName() + "消费商品,此时商品为:" + --store);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
/**
* 消费者
*/
class Customer1 implements Runnable {
private Store1 store;
//构造器
public Customer1(Store1 store) {
this.store = store;
}
@Override
public void run() {
//调用生产方法,生成二十个
for (int i = 0; i <= 20; i++) {
store.customer();
}
}
}
/**
* 生产者
*/
class Productor1 implements Runnable {
private Store1 store;
//构造器
public Productor1(Store1 store) {
this.store = store;
}
@Override
public void run() {
for (int i = 0; i <= 20; i++) {
store.producter();
}
}
}