NO3锁相关内容
作者:互联网
目录
参考内容:
https://www.cnblogs.com/dolphin0520/p/3920373.html
https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E5%B9%B6%E5%8F%91.md#%E4%BA%94%E4%BA%92%E6%96%A5%E5%90%8C%E6%AD%A5
<码出高效>
概述
为什么出现了锁操作的概念,首先需要说明一下 电脑的内存设计
电脑内存设计简要说明
电脑中分为两块 一个是主存, 一个是缓存,
主存也称为物理内存,即使在断电情况下数据也不会丢失(除非用锤子把硬件砸了哈哈),但是缓存则不会进行物理存入很容易丢失,但是缓存有自己的优点就是,读取速率极快.
所以目前cpu处理数据的方式时,先查看缓存,看是否有此条数据,有的话直接使用,没有的话就再访问主存.
java内存模型
然后需要说明一下 java的内存模型. 每个线程之间,都有自己独立的缓存,相同的主存.并且线程只能操作自己缓存内的数据,不能操作主存数据和其他线程缓存中的数据.
这样很容易引发以下场景:
想要通过 两个线程 每个线程给 主存中的i=0 进行+1 操作
主存中存储i= 0
线程1读取主存i=0 放入自己的缓存中 执行i+1
在线程1没有 将自己缓存中的 i= 1刷新到主存中时,
线程2 开始读取主存 ,此时 i = 0; 线程2进行+1操作, i=1.
并没有达到 i =2 的效果
package JavaThread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class NO22ThreadDemo1 {
private static int num = 0;
//此种情况,如果使用线程安全的类,则不会出现问题
//private static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0 ; i < 100 ; i++){
executorService.submit(new Runnable() {
@Override
public void run() {
num = num + 1;
}
});
}
//让主线程 最后再输出,因为有可能出现线程池中线程未执行直接,主线程先输出结束的情况
Thread.sleep(10000);
System.out.println("num : " + num);
}
}
并没有达到加到100的效果
出现线程不安全的情况
满足线程安全的三个特性
原子性
每一个操作都为原子操作,不可进行拆分 比如 int num = 1, 但是 num = num + 1,就不是原子操作了.
一致性
每次对数据进行更改,其他相关操作都是可见的,能及时进行更新
可以使用volatile原语进行保证(当某一个线程对变量进行修改之后,可以使其他缓存中次变量失效,重新读取主存中数据)
有序性
按照代码顺序执行操作
如何保证线程安全
- 使用线程安全的类
- 保证单线程对数据进行修改
- 只进行查询,不修改
- 通过加锁操作来保证线程安全
锁
锁分为 乐观锁 悲观锁两种
乐观锁
一般是CAS , 首先会有三个值, 存储地址,预期值,新值.
首先去存储地址查询 值,然后与预期值进行比较,看是否相同
相同则更新为新值,不同则更改预期值,再次查询比较.或者其他操作
弊端:
- ABA问题,从A 变为B 再变为A,则判断没有发生变化,会引发异常, 这个可以通过版本号机制解决.每次修改更新版本号
- 流程较长涉及多修改操作则无法使用此方法.
实例:
java中的 AtomicInteger等类使用此方法保证线程安全
悲观锁
对线程不安全的操作进行加锁
分为两种:
synchronized
原理
基于JVM实现的, 每个类对应都有一个monitor监控器,当线程调用加锁操作,会持有此监控器,其他线程只有等待此线程释放后才能调用操作.
经过版本更新,提出了 偏向锁,轻量级锁,重量级锁的概念.
- 当线程第一次调用加锁操作时,将monitor中的id 重置为threadId,变为偏向锁
- 在锁持有期间,其他线程再次调用,会判断id 与新线程的threadId是否相同,不同则变为轻量级锁
- 当多个不同线程访问的时候,升级为重量级锁
适用场景:
- 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
- 轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
- 重量级锁:有实际竞争,且锁竞争时间长。
使用
对类进行加锁操作, 此类产生的所有对象都会影响
1. synchronized互斥同步
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。
1.1 代码块同步
基本语法:
public void func(){
synchronized(this){
//.....逻辑部分
}
}
只作用于一个对象,同时执行一个对象中的同步代码块的时候才会进行同步,执行两个对象中各自的代码块时候不会进行同步.
例子:
package JavaThread.Concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedBlock1 {
public void function1(){
synchronized(this){
for (int i=0; i<10 ;i++){
System.out.print(i);
}
}
}
public static void main(String [] args){
SynchronizedBlock1 synchronizedBlock1 = new SynchronizedBlock1();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(()->synchronizedBlock1.function1());
service.execute(()->synchronizedBlock1.function1());
}
}
结果
01234567890123456789
由于加入了同步操作,需要第一次调用之后才能到第二次调用
例子2:
package JavaThread.Concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedBlock2 {
public void function1(){
synchronized(this){
for (int i=0; i<10 ;i++){
System.out.print(i);
}
}
}
public static void main(String [] args){
SynchronizedBlock2 synchronizedBlock1 = new SynchronizedBlock2();
SynchronizedBlock2 synchronizedBlock2 = new SynchronizedBlock2();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(()->synchronizedBlock1.function1());
service.execute(()->synchronizedBlock2.function1());
service.shutdownNow();
}
}
结果
00112345263789456789
调用两个对象中的同步代码块时不会保证同步
1.2 同步一个方法
基本语法:
public synchronized void func () {
// ...
}
类似于同步代码块, 是针对于对象同步的.
例子:
package JavaThread.Concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedMethod1 {
public synchronized void function1() {
for (int i = 0; i < 10; i++) {
System.out.print(i);
}
}
public static void main(String[] args) {
SynchronizedMethod1 synchronizedBlock1 = new SynchronizedMethod1();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> synchronizedBlock1.function1());
service.execute(() -> synchronizedBlock1.function1());
}
}
1.3 同步一个类
基本语法:
public void func() {
synchronized (SynchronizedExample.class) {
// ...
}
}
即使调用一个类的不同对象 也可以保证同步
例子:
package JavaThread.Concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedClass1 {
public void function1() {
synchronized(SynchronizedBlock1.class) {
for (int i = 0; i < 10; i++) {
System.out.print(i);
}
}
}
public static void main(String[] args) {
SynchronizedClass1 synchronizedClass1 = new SynchronizedClass1();
SynchronizedClass1 synchronizedClass2 = new SynchronizedClass1();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> synchronizedClass1.function1());
service.execute(() -> synchronizedClass2.function1());
}
}
结果:
01234567890123456789
1.4 同步静态方法
基本语法:
public synchronized static void fun() {
// ...
}
由于静态方法的特殊性(很早进行加载,而且唯一)
所以也可以实现 对类的方法进行加锁.
lock
原理
基于jdk实现,是自旋锁, 其中维护一个 int volatile state变量, 当线程1持有锁后,state变为1, 此时 线程2无法操作,但是线程1还可以再次持有锁,state变为2, 然后只有等线程1释放两次后,state变为2, 线程2才可以进行争抢.
juc包中 工具部分基于此实现
使用
是JUC包中的锁
例子:
package JavaThread.Concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
public class ReentranLock1 {
public void function1(){
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
for (int i=0; i<10 ;i++){
System.out.print(i);
}
}finally {
lock.unlock();//及时释放锁 避免死锁
}
}
public static void main(String[] args) {
ReentranLock1 reentranLock1 = new ReentranLock1();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> reentranLock1.function1());
service.execute(() -> reentranLock1.function1());
}
}
两者比较
目前 一般都使用synchronized. 后续真正使用lock再进行分析比较
标签:function1,java,util,线程,内容,import,相关,public,NO3 来源: https://www.cnblogs.com/yaoxublog/p/11001865.html