JUC并发知识_并行与并发

JUC并发知识_并行与并发文章目录lock和synchronized的区别Condition集合类的并发问题Callablelock和synchronized的区别synchronized 自动释放所,lock必须手动释放synchronized 如果获取不到锁就一直会等待下取,lock可以不用(trylock()方法)lock是可中断锁,而synchronized 不是可中断锁(tryLock(long timeout,TimeUnit unit)方法)synchronized 是可重入锁,lock也是可重入锁可

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

lock和synchronized的区别

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

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

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


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()的其他情况被唤醒,而此时是不应该唤醒的。

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

Condition

Condition的作用相当于synchronized中的

Condition condition = new Condition();
condition.await();//相当于wait()
condition.signal();//相当于notify()

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

集合类的并发问题

List<String> list = new CopyOnWriteArrayList()	//这个列表是线程安全的
//同理还有Set和HashMap的线程安全集合

Callable

创建线程的有一种方法

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

CountDownLanuch

CountDownLanuch是减法计数器

  • countDown 计数器-1
  • await()等待计数器归零然后向下执行
  • 通常用于某一任务必须被执行才执行其他任务
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

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

Semaphere semaphere = new Semaphere(3);	//停车位数量
semaphere.acquire();	//获取
semaphere.release();	//释放

ReadWriteLock读写锁

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

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

线程池

ExecutorService threadPool = Executors.newSingleThreadExecutor();	//单一实例
ExecutorService threadPool = Executors.newFixedThreadPool(5);	//固定数量
ExecutorService threadPool = Executors.newCachedThreadPool(5);	//可伸缩型,遇强则强遇弱则弱

创建线程
threadPool.execute();

在这里插入图片描述
在这里插入图片描述

Future异步请求

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

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,如何保证原子性,不被其他进程打断?
使用原子类,解决原子性问题

volatile static AtomicInteger num = new AtomicInteger(0);

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

  1. 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。

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

单例模式

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举例说明:

public final int getAndIncrement() { 

return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int incrementAndGet() { 

return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
/** * 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问题, 引入原子引用 ! 对应的思想: 乐观锁

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();
}
}

公平锁, 非公平锁

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

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

 	public ReentrantLock() { 

sync = new NonfairSync(); //无参默认非公平锁
}
public ReentrantLock(boolean fair) { 

sync = fair ? new FairSync() : new NonfairSync();//传参为true为公平锁
}

自旋锁

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

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

/** * 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)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

死锁

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

案例

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定位进程号
    在这里插入图片描述
  2. 使用jstack 查看进程信息找到死锁问题
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.
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • NSGA2算法详解「建议收藏」

    NSGA2算法详解「建议收藏」目录预备知识多目标优化问题的解NSGA-II简介NSGA-II详细介绍参数设置问题约束处理方法1.预备知识多目标优化的相关知识:https://blog.csdn.net/haha0332/article/details/88634378支配:假设小明9岁,50斤,小红8岁,45斤,小明无论是岁数还是体重都比小红大,所以小明支配小红。互不支配:假设小明7岁,50斤,小红8岁,45斤,小明岁数比小红小,但体重比小红大,所以小明和小红互不支配。帕累托集:在这个集合中,任意两个解互不

  • aliddns ipv6_linux系统下配置阿里DDNS(IPv6)

    aliddns ipv6_linux系统下配置阿里DDNS(IPv6)IPv6日渐完善,家里的宽带和手机也都分配了ipv6全球单播地址,手机分到了/64,宽带更是分到了/56。测试了一下运营商内外的连通性也都还可以,基本能跑满带宽,IPv6终于可以用起来了,个个都是公网,不用再渴求ipv4和搞内网穿透了。适用此教程适用于基于linux的各种系统,ubuntu、centos、openwrt、群晖等等,本文只介绍IPv6,不涉及IPv4。代码aliddnsipv6_a…

  • quotient函数_Mid函数

    quotient函数_Mid函数今天学到一个新函数,很有用QuotedStr(s);//在s两边加单引号,这样就不会看着n多的单引号糊涂了。。。转载于:https://www.cnblogs.com/studypanp/p/4917369.html…

    2022年10月18日
  • kali更新源失败解决办法

    kali更新源失败解决办法kali更新源失败哪怕是更换了多家的镜像站,但是依旧失败,并且etc/apt/source.list.d下并无文件而后搜寻资料,得知可以通过更新本地虚拟机内的密钥来解决此问题:showuthecode:sudoapt-keyadv–keyserverhkp://keys.gnupg.net–recv-keys7D8D0BF6参考资料…

  • c++runtime_c=2πr

    c++runtime_c=2πr转自:https://blog.csdn.net/BlackRose2013/article/details/7670820用fstream在指定文件流模式的情况下也可以自动新建文件:fstreamoo(“aa.txt”,ofstream::out);在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符…

  • 系统安装失败如何恢复原系统_安装失败显示其他原因

    系统安装失败如何恢复原系统_安装失败显示其他原因按照博客上的方法安装了npm,但是安装cnpm出现了“cnpm不是内部命令”那么我就详细的来说下解决方法;1,node.js的安装在node.js官网下载,根据你电脑的选择版本,我的安装路径是D盘,D:\ProgramFiles\nodejs输入npm-v检测是否安装成功。2.安装cnpmnpminstall-gcnpm–registry=https://registry.npm.taobao.org(注意要对环境变量配置)在系统变量path下添加该路径D:\Prog

    2022年10月15日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号