前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JUC并发知识_并行与并发

JUC并发知识_并行与并发

作者头像
全栈程序员站长
发布2022-09-21 20:59:31
2740
发布2022-09-21 20:59:31
举报

大家好,又见面了,我是你们的朋友全栈君。

文章目录

lock和synchronized的区别

  • synchronized 自动释放所,lock必须手动释放
  • synchronized 如果获取不到锁就一直会等待下取,lock可以不用(trylock()方法)
  • lock是可中断锁,而synchronized 不是可中断锁(tryLock(long timeout,TimeUnit unit)方法)
  • synchronized 是可重入锁,lock也是可重入锁

可重入锁:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

代码语言:javascript
复制
public class Lock{ 
   
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock()
            throws InterruptedException{ 
   
        Thread thread = Thread.currentThread();
        while(isLocked && lockedBy != thread){ 
   
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = thread;		//第一次的时将lockedBy赋值为改线程
    }
    public synchronized void unlock(){ 
   
        if(Thread.currentThread() == this.lockedBy){ 
   
            lockedCount--;
            if(lockedCount == 0){ 
   
                isLocked = false;
                notify();
            }
        }
    }
}

注意虚假唤醒现象 虚假唤醒就是一些obj.wait()会在除了obj.notify()和obj.notifyAll()的其他情况被唤醒,而此时是不应该唤醒的。

代码语言:javascript
复制
	if()
		wait();	//如果被阻塞,那么下一次唤醒后就直接不用等待,直接执行下面的语句
	//改为
	while()
		wait();

Condition

Condition的作用相当于synchronized中的

代码语言:javascript
复制
Condition condition = new Condition();
condition.await();//相当于wait()
condition.signal();//相当于notify()

作用 主要应用于同步机制,比如A事件做完了才能做B事件

集合类的并发问题

代码语言:javascript
复制
List<String> list = new CopyOnWriteArrayList()	//这个列表是线程安全的
//同理还有Set和HashMap的线程安全集合

Callable

创建线程的有一种方法

  • 可以有返回值
  • 可以抛出异常
  • 调用call()方法

CountDownLanuch

CountDownLanuch是减法计数器

  • countDown 计数器-1
  • await()等待计数器归零然后向下执行
  • 通常用于某一任务必须被执行才执行其他任务
代码语言:javascript
复制
public static void main(String[] args) throws InterruptedException { 
   
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for(int i = 0;i < 6;i ++){ 
   
            new Thread(() -> { 
   
                System.out.println(Thread.currentThread().getName() + "go out");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        countDownLatch.await();     //等待计数器归零 然后向下执行
        System.out.println("Close door");
    }

CyclicBarrier是加法计数器

Semaphere

一般用于只有固定数量的资源

代码语言:javascript
复制
Semaphere semaphere = new Semaphere(3);	//停车位数量
semaphere.acquire();	//获取
semaphere.release();	//释放

ReadWriteLock读写锁

写的时候只能有一个去写,当时可以同时有多个去读

代码语言:javascript
复制
ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();

BlockQueue四大组件

add,remove如果不正确会抛出异常 put,take如果不正确会等待

SynchronousQueue

和其他的BlockingQueue不一样,SynchronousQueue不存储元素,put了一个元素后,只能先take取出来,才能继续put

线程池

代码语言:javascript
复制
ExecutorService threadPool = Executors.newSingleThreadExecutor();	//单一实例
ExecutorService threadPool = Executors.newFixedThreadPool(5);	//固定数量
ExecutorService threadPool = Executors.newCachedThreadPool(5);	//可伸缩型,遇强则强遇弱则弱

创建线程
threadPool.execute();
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Future异步请求

以下线程都是异步方法,这里的异步类似于ajax异步请求

代码语言:javascript
复制
CompletableFuture<Void>completableFutrue = CompletableFuture.runAsync(...)		//没有返回值的runAsync
CompletableFuture<Void>completableFutrue = CompletableFuture.supplyAsync(...)		//有返回值的supplyAsync()

JMM

Java的并发采用的是共享内存模型

JMM规范: 线程解锁前,必须把共享变量立刻刷回主存 线程加锁前,必须读取主存中的最新值到工作内存中! 加锁和解锁必须是同一把锁 JMM模型 java内存模型中规定了所有变量都存贮到主内存(如虚拟机物理内存中的一部分)中。每一个线程都有一个自己的工作内存(如cpu中的高速缓存)。线程中的工作内存保存了该线程使用到的变量的主内存的副本拷贝。线程对变量的所有操作(读取、赋值等)必须在该线程的工作内存中进行。不同线程之间无法直接访问对方工作内存中变量。线程间变量的值传递均需要通过主内存来完成。

关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:

关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
  • unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

  • (1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
  • (2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • (3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • (4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • (5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • (6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • (7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
  • (8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。
在这里插入图片描述
在这里插入图片描述

Volatile

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

可见性 被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。


真正实现可见性的是Synchronized的两条规定 1、线程解锁前,必须把共享变量的最新值刷新到主内存中; 2、线程加锁时,讲清空工作内存中共享变量的值,从而使用共享变量是需要从主内存中重新读取最新的值(加锁与解锁需要统一把锁)

线程执行互斥锁代码的过程: 1.获得互斥锁 2.清空工作内存 3.从主内存拷贝最新变量副本到工作内存 4.执行代码块 5.将更改后的共享变量的值刷新到主内存中 6.释放互斥锁

所以synchronized具有原子可见性

不保证原子性 再执行过程中,会被其他进程打断。 问题 如果不加lock和synchronized,如何保证原子性,不被其他进程打断? 使用原子类,解决原子性问题

代码语言:javascript
复制
volatile static AtomicInteger num = new AtomicInteger(0);

指令重排 什么是指令重排: 你写的程序,计算机并不是按照指定的的步骤执行 源代码—>编译器优化源代码–>指令并行也可能会重排—>内存系统也会重排 执行 在一个变量被volatile修饰后,JVM会为我们做两件事:

  1. 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。
  2. 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。
在这里插入图片描述
在这里插入图片描述

单例模式

代码语言:javascript
复制
package com.czp.single;

import java.lang.reflect.Constructor;

public class LazyManThread { 
   

    private static volatile LazyManThread lazyManThread = null;

    private static boolean isExist = false;

    private LazyManThread() { 
   
        synchronized (LazyManThread.class) { 
   
            if (!isExist) { 
   
                isExist = true;
            } else { 
   
                throw new RuntimeException("禁止使用反射创建该对象");
            }
        }

    }

    //
    private LazyManThread(int a){ 
   
        synchronized (LazyManThread.class){ 
   
            if(lazyManThread != null){ 
   
                throw new RuntimeException("禁止使用反射创建该对象");
            }
        }
    }

    public static LazyManThread getInstance() { 
   
        //if只会判断一次,当两个线程同时判断时一个线程就会在同步代码块中等待
        if (lazyManThread == null) { 
   
            //不直接使用同步的原因,提高执行效率
            synchronized (LazyManThread.class) { 
   
                if (lazyManThread == null) { 
   
                    lazyManThread = new LazyManThread();
                }
            }
        }

        /** * 由于对象创建不是原子性操作 * 1. 分配内存空间 * 2. 使用构造器创建对象 * 3. 将对象指向内存空间 */
        /** * 可能会发生指令重排 * 123 * * 132 * * 这是就需使用volatile关键字来防止指令重排 */
        return lazyManThread;
    }


    public static void main(String[] args) throws Exception { 
   
// LazyManThread instance = LazyManThread.getInstance();

        Constructor<LazyManThread> declaredConstructor = LazyManThread.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazyManThread lazyManThread = declaredConstructor.newInstance();
        LazyManThread instance = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(lazyManThread);
    }
}

CAS

CAS(Compare and Swap),是比较并替换的意思。

原子操作类 所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。如AtomicBoolean,AtomicInteger,AtomicLong,它们分别用于Boolean,Integer,Long类型的原子性操作。这里的Atomic操作类的底层正是使用了“CAS机制”。

Java语言CAS底层如何实现?

利用unsafe提供了原子性操作方法。以AtomicInteger举例说明:

代码语言:javascript
复制
public final int getAndIncrement() { 
   
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
 
public final int incrementAndGet() { 
   
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
代码语言:javascript
复制
/** * Atomically adds the given value to the current value of a field * or array element within the given object <code>o</code> * at the given <code>offset</code>. * * @param o object/array to update the field/element in * @param offset field/element offset * @param delta the value to add * @return the previous value * @since 1.8 */
public final int getAndAddInt(Object o, long offset, int delta) { 
   
    int v;
    do { 
   
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));		//compareAndSwapInt整个比较并替换的操作是一个原子操作。
    return v;
}

Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。

至于valueOffset,是通过unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单的把valueOffset理解为value变量的内存地址。

正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。

CAS缺点

  • 并发量较高时CPU开销过大

在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。

  • 只能保证单个变量的原子性

CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

  • ABA问题

ABA问题的根本在于cas在修改变量的时候,无法记录变量的状态,比如修改的次数,否修改过这个变量。这样就很容易在一个线程将A修改成B时,另一个线程又会把B修改成A,造成casd多次执行的问题。

解决ABA问题, 引入原子引用 ! 对应的思想: 乐观锁

代码语言:javascript
复制
package com.czp.CAS;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo { 
   

    public static void main(String[] args) { 
   
        //integer
        AtomicStampedReference<Integer> stamp = new AtomicStampedReference<>(1, 1);

        new Thread(()->{ 
   
            System.out.println("a1=>" + stamp.getStamp());
            try { 
   
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) { 
   
                e.printStackTrace();
            }
            stamp.compareAndSet(1, 2, stamp.getStamp(), stamp.getStamp()+1);
            System.out.println("a2=>" + stamp.getStamp());
            stamp.compareAndSet(2, 1, stamp.getStamp(), stamp.getStamp()+1);
            System.out.println("a3=>" + stamp.getStamp());
        }).start();

        new Thread(()->{ 
   
            System.out.println("b1=>" + stamp.getStamp());
            stamp.compareAndSet(1, 2, stamp.getStamp(), stamp.getStamp() + 1);
            System.out.println("b2=>" + stamp.getStamp());
        }).start();

    }
}

公平锁, 非公平锁

公平锁: 非常公平,先来后到,不允许插队

非公平锁: 非常不公平, 允许插队

代码语言:javascript
复制
 	public ReentrantLock() { 
   
        sync = new NonfairSync(); //无参默认非公平锁
    }
 	public ReentrantLock(boolean fair) { 
   
        sync = fair ? new FairSync() : new NonfairSync();//传参为true为公平锁
    }

自旋锁

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。

代码语言:javascript
复制
/** * Date: 2016年1月4日 下午4:41:50 * * @author medusar */

public class SpinLock { 
   

private AtomicReference cas = new AtomicReference();

	public void lock() { 
   

		Thread current = Thread.currentThread();

		// 利用CAS
	
		while (!cas.compareAndSet(null, current)) { 
   

		// DO nothing

		}

	}

	public void unlock() { 
   

		Thread current = Thread.currentThread();
	
		cas.compareAndSet(current, null);

	}

}

ock()方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用unlock方法释放了该锁。 缺点

  1. 如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
  2. 上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

优点 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快

非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

死锁

  • 死斥
  • 占有等待
  • 循环等待
  • 不可抢占

案例

代码语言:javascript
复制
package com.czp.lock;

import java.util.concurrent.TimeUnit;

public class KillLock implements Runnable { 
   
    private String stringA;
    private String stringB;

    public KillLock(String stringA, String stringB) { 
   
        this.stringA = stringA;
        this.stringB = stringB;
    }

    @Override
    public void run() { 
   
        synchronized (stringA) { 
   

            System.out.println(Thread.currentThread().getName() + "lock" + stringA + "try to lock stringB");
            try { 
   
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) { 
   
                e.printStackTrace();
            }
            synchronized (stringB) { 
   
                System.out.println(Thread.currentThread().getName() + "lock" + stringB + "try to lock stringA");
            }
        }
    }

    public static void main(String[] args) { 
   
        String a = "a";
        String b = "b";

        new Thread(new KillLock(a, b)).start();
        new Thread(new KillLock(b, a)).start();
    }
}
  1. 使用jps -l定位进程号
在这里插入图片描述
在这里插入图片描述
  1. 使用jstack 查看进程信息找到死锁问题
代码语言:javascript
复制
Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.czp.lock.KillLock.run(KillLock.java:25)
        - waiting to lock <0x00000000d5f169c8> (a java.lang.String)
        - locked <0x00000000d5f169f8> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:745)
"Thread-0":
        at com.czp.lock.KillLock.run(KillLock.java:25)
        - waiting to lock <0x00000000d5f169f8> (a java.lang.String)
        - locked <0x00000000d5f169c8> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/168798.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • lock和synchronized的区别
  • Condition
  • 集合类的并发问题
  • Callable
  • CountDownLanuch
  • Semaphere
  • ReadWriteLock读写锁
  • BlockQueue四大组件
  • SynchronousQueue
  • 线程池
  • Future异步请求
  • JMM
  • Volatile
  • 单例模式
  • CAS
  • 公平锁, 非公平锁
  • 自旋锁
  • 死锁
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档