悲观锁(Synchronized)和乐观锁(CAS)

2023-11-05

悲观锁和乐观锁

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

Synchronized

在Java1.5及以前的版本中,synchronized并不是同步最好的选择,由于并发时频繁的阻塞和唤醒线程,会浪费许多资源在线程状态的切换上,导致了synchronized的并发效率在某些情况下不如ReentrantLock。
例如: 当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。而ReentrantLock可以有多个Condition。
更多关于ReentrantLock和Synchronized

在Java1.6的版本中,对synchronized进行了许多优化,极大的提高了synchronized的性能。只要synchronized能满足使用环境,建议使用synchronized而不使用ReentrantLock。
动态高并发时推荐使用ReentrantLock而不是Synchronized
synchronized和ReentrantLock的区别

Synchronized使用

三种方式:

修饰实例方法,为当前实例加锁,进入同步方法前要获得当前实例的锁。
修饰静态方法,为当前类对象加锁,进入同步方法前要获得当前类对象的锁。
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。

有一个要注意的地方是对静态方法的修饰可以和实例方法的修饰同时使用,不会阻塞,因为一个是修饰的Class类,一个是修饰的实例对象。下面的例子可以说明这一点:

public class SynchronizedTest {

	public static synchronized void StaticSyncTest() {

		for (int i = 0; i < 3; i++) {
			System.out.println("StaticSyncTest");
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public synchronized void NonStaticSyncTest() {

		for (int i = 0; i < 3; i++) {
			System.out.println("NonStaticSyncTest");
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public static void main(String[] args) throws InterruptedException {

    SynchronizedTest synchronizedTest = new SynchronizedTest();
    new Thread(new Runnable() {
		@Override
		public void run() {
			SynchronizedTest.StaticSyncTest();
		}
	}).start();
    new Thread(new Runnable() {
		@Override
		public void run() {
			synchronizedTest.NonStaticSyncTest();
		}
	}).start();
}

//StaticSyncTest
//NonStaticSyncTest
//StaticSyncTest
//NonStaticSyncTest
//StaticSyncTest
//NonStaticSyncTest

代码中我们开启了两个线程分别锁定静态方法和实例方法,从打印的输出结果中我们可以看到,这两个线程锁定的是不同对象,可以并发执行。

Synchronized底层原理

我们看一段synchronized关键字经过编译后的字节码:

if (null == instance) {   
	synchronized (DoubleCheck.class) {
		if (null == instance) {   
			instance = new DoubleCheck();   
		}
	}
}

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

可以看到synchronized关键字在同步代码块前后加入了monitorenter和monitorexit这两个指令。monitorenter指令会获取锁对象,如果获取到了锁对象,就将锁计数器加1,未获取到则会阻塞当前线程。monitorexit指令会释放锁对象,同时将锁计数器减1。

Java1.6对Synchronized的优化

JDK1.6对对synchronized的优化主要体现在引入了“偏向锁”和“轻量级锁”的概念,同时synchronized的锁只可升级,不可降级
在这里插入图片描述

偏向锁:

偏向锁的思想是指如果一个线程获得了锁,那么就从无锁模式进入偏向模式,这一步是通过CAS操作来做的,进入偏向模式的线程每一次访问这个锁的同步代码块时都不需要再进行同步操作,除非有其他线程访问这个锁。
偏向锁提高的是那些带同步但无竞争的代码的性能,也就是说如果你的同步代码块很长时间都是同一个线程访问,偏向锁就会提高效率,因为他减少了重复获取锁和释放锁产生的性能消耗。如果你的同步代码块会频繁的在多个线程之间访问,可以使用参数-XX:-UseBiasedLocking来禁止偏向锁产生,避免在多个锁状态之间切换。

轻量级锁:

偏向锁优化了只有一个线程进入同步代码块的情况,当多个线程访问锁时偏向锁就升级为了轻量级锁。
轻量级锁的思想是当多个线程进入同步代码块后,多个线程未发生竞争时一直保持轻量级锁,通过CAS来获取锁。如果发生竞争,首先会采用CAS自旋操作来获取锁,自旋在极短时间内发生,有固定的自旋次数,一旦自旋获取失败,则升级为重量级锁。
轻量级锁优化了多个线程进入同步代码块的情况,多个线程未发生竞争时,可以通过CAS获取锁,减少锁状态切换。当多个线程发生竞争时,不是直接阻塞线程,而是通过CAS自旋来尝试获取锁,减少了阻塞线程的概率,这样就提高了synchronized锁的性能。

synchronized的等待唤醒机制

synchronized的等待唤醒是通过notify/notifyAll和wait三个方法来实现的,这三个方法的执行都必须在同步代码块或同步方法中进行,否则将会报错。

wait方法的作用是使当前执行代码的线程进行等待,notify/notifyAll相同,都是通知等待的代码继续执行,notify只通知任一个正在等待的线程,notifyAll通知所有正在等待的线程。wait方法跟sleep不一样,他会释放当前同步代码块的锁,notify在通知任一等待的线程时不会释放锁,只有在当前同步代码块执行完成之后才会释放锁。下面的代码可以说明这一点:

public static void main(String[] args) throws InterruptedException {
    waitThread();
    notifyThread();
}

private static Object lockObject = new Object();
	
private static void waitThread() {
    
    Thread watiThread = new Thread(new Runnable() {
        
        @Override
        public void run() {
            
            synchronized (lockObject) {
                System.out.println(Thread.currentThread().getName() + "wait-before");
                
                try {
                    TimeUnit.SECONDS.sleep(2);
                    lockObject.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                System.out.println(Thread.currentThread().getName() + "after-wait");
            }
            
        }
    },"waitthread");
    watiThread.start();
}

private static void notifyThread() {
    
    Thread watiThread = new Thread(new Runnable() {
        
        @Override
        public void run() {
            
            synchronized (lockObject) {
                System.out.println(Thread.currentThread().getName() + "notify-before");
                
                lockObject.notify();
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
                
                System.out.println(Thread.currentThread().getName() + "after-notify");
            }
            
        }
    },"notifythread");
    watiThread.start();
}

//waitthreadwait-before
//notifythreadnotify-before
//notifythreadafter-notify
//waitthreadafter-wait

代码中notify线程通知之后wait线程并没有马上启动,还需要notity线程执行完同步代码块释放锁之后wait线程才开始执行。

CAS

在synchronized的优化过程中我们看到大量使用了CAS操作,CAS全称Compare And Set(或Compare And Swap),CAS包含三个操作数:内存位置(V)、原值(A)、新值(B)。简单来说CAS操作就是一个虚拟机实现的原子操作,这个原子操作的功能就是将旧值(A)替换为新值(B),如果旧值(A)未被改变,则替换成功,如果旧值(A)已经被改变则什么都不做。进入一个自旋操作,即不断的重试。

CAS使用

可以通过AtomicInteger类的自增代码来说明这个问题,当不使用同步时下面这段代码很多时候不能得到预期值10000,因为noncasi[0]++不是原子操作,代码如下:

private static void IntegerTest() throws InterruptedException {

    final Integer[] noncasi = new Integer[]{ 0 };

    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int j = 0; j < 1000; j++) {
                    noncasi[0]++;
                }
            }
        });
        thread.start();
    }
    
    while (Thread.activeCount() > 2) {
        Thread.sleep(10);
    }
    System.out.println(noncasi[0]);
}

//7889

当使用AtomicInteger的getAndIncrement方法来实现自增之后相当于将casi.getAndIncrement()操作变成了原子操作:

private static void AtomicIntegerTest() throws InterruptedException {

    AtomicInteger casi = new AtomicInteger();
    casi.set(0);

    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int j = 0; j < 1000; j++) {
                    casi.getAndIncrement();
                }
            }
        });
        thread.start();
    }
    while (Thread.activeCount() > 2) {
        Thread.sleep(10);
    }
    System.out.println(casi.get());
}

//10000

当然也可以通过synchronized关键字来达到目的,但CAS操作不需要加锁解锁以及切换线程状态,效率更高。

再来看看casi.getAndIncrement()具体做了什么,在JDK1.8之前getAndIncrement是这样实现的(类似incrementAndGet):

private volatile int value;

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

通过compareAndSet将变量自增,如果自增成功则完成操作,如果自增不成功,则自旋进行下一次自增,由于value变量是volatile修饰的,通过volatile的可见性,每次get()都能获取到最新值,这样就保证了自增操作每次自旋一定次数之后一定会成功。

JDK1.8中则直接将getAndAddInt方法直接封装成了原子性的操作,更加方便使用:

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

CAS底层原理

CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。
而compareAndSwapInt就是借助C来调用CPU底层指令实现的。
更多参考:JAVA CAS原理深度分析

CAS的缺陷

1.ABA问题

问题:

因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

解决方法:

从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2.循环开销过大

问题:

前面说过,如果旧值(A)已经被改变,就会进入自旋操作。
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。例如,Unsafe下的getAndAddInt方法会一直循环,知道成功才会返回。

解决方案:

如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3.只能保证一个共享变量的原子操作

问题:

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。

解决方案;

可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

concurrent包的实现

CAS操作是实现Java并发包的基石,他理解起来比较简单但同时也非常重要。Java并发包就是在CAS操作和volatile基础上建立的

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

A线程写volatile变量,随后B线程读这个volatile变量。
A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

首先,声明共享变量为volatile;
然后,使用CAS的原子条件更新来实现线程之间的同步;
同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

下图中列举了J.U.C包中的部分类支撑图:
在这里插入图片描述

参考链接:Java并发(4)- synchronized与CAS
参考链接:JAVA CAS原理深度分析

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

悲观锁(Synchronized)和乐观锁(CAS) 的相关文章

  • Java 中使用同步块的并发未给出预期结果

    下面是一个简单的 java 程序 它有一个名为 cnt 的计数器 该计数器会递增 然后添加到名为 monitor 的列表中 cnt 由多个线程递增 值由多个线程添加到 monitor 在方法 go 的末尾 cnt 和 monitor siz
  • javax.net.ssl.SSLHandshakeException:java.security.cert.CertificateException:不存在主题备用名称

    基本上 我有一个测试服务器 基于 Linux 带有公共 IP 机器人 没有公共主机名 所以我尝试使用 IP 地址为其创建 ssl 证书 这样我的 Java 应用程序就可以使用 IP 地址访问另一个应用程序 例如 https 210 10 1
  • 为什么同步字段变量并在同步块内递增它会导致打印乱序?

    我有一个简单的代码片段 public class ItemManager private Integer itemCount 0 public void incrementAndPrint synchronized this System
  • 在lockObject上同步和使用this作为锁有什么区别?

    我知道同步方法和同步块之间的区别 但我不确定同步块部分 假设我有这个代码 class Test private int x 0 private Object lockObject new Object public void incBloc
  • 如何使用 Devise 将 Rails 应用程序转变为 SSO/CAS 服务器?

    我从一个上一个问题我一直在问错误的问题 我想将我的应用程序变成 CAS 服务器 以便应用程序的管理员可以使用相同的身份验证机制来登录我们为组织开发的其他应用程序 你以前做过这个吗 是否有一个插件可以增加 Devise 充当 CAS 服务器的
  • 多线程案例:银行取钱

    不安全取钱 两个人去银行取钱 账户 银行取钱 给账户上锁 public class UnsafeBank public static void main String args 账户 Account3 account new Account
  • 多线程案例:购买车票

    购票案例 多线程同步 多线程的并发执行虽然可以提高程序的效率 但是 当多个线程去访问同一个资源时 也会引发一些安全问题 并发 同一个对象被多个线程同时操作 处理多线程问题时 多个线程访问同一个对象 并且某些线程还想修改这个对象 这时候我们就
  • 如何检查对象是否正在@synchronized

    有时我会编写以下代码来同步例程 synchronized objToBeSync 当两个线程尝试同时访问同步块时 其中一个线程会阻塞其他线程 直到其中一个线程退出同步块 但是 有时我不希望一个对象阻塞另一个对象 但其他对象检查对象是否正在同
  • java 中的同步 - 正确使用

    我正在构建一个在多进程 线程 中使用的简单程序 我的问题更容易理解 什么时候我必须使用保留字同步 我是否需要在影响骨骼变量的任何方法中使用这个词 我知道我可以将它放在任何非静态的方法上 但我想了解更多 谢谢你 这是代码 public cla
  • 有没有办法有条件地应用注释?

    在我的 java play 应用程序中 我有注释 RequiresAuthentication clientName CasClient 在我的控制器内 我只想在生产环境中对用户进行身份验证 如何有条件地应用注释 如果我处理身份验证的方式是
  • 使用 Grails Spring Security CAS 插件重定向循环

    我正在处理一个涉及 CAS 服务器的项目 该项目使用单点登录 SSO 与其他基于 Spring 的项目一起使用 但我收到了涉及 Grails spring security cas 插件的重定向循环 版本 spring security C
  • 同步与 ReadWriteLock 性能

    我试图证明当有很多读者而只有一些作者时同步会更慢 不知怎的 我证明了相反的情况 以 RW 为例 执行时间为 313 ms package zad3readWriteLockPerformance import java util Array
  • Java 同步引用

    我有A级和B级 public class A private static List
  • jasig cas 重定向过多问题

    我正在尝试使用 spring security 和 spring security cas 带有 Jasig CAS 的 SSO 来保护 spring boot Web 应用程序 尝试访问受保护的资源时 我遇到了太多重定向错误 该项目可用h
  • 浏览器不遵循 AJAX 响应的重定向(PHP 生成的响应使用 CAS 身份验证)

    好吧 看来我最初的问题犯了一个错误 因此 这里有一些更正 答案仍然适用 因为当协议更改为 HTTPS SSL 时 第二个重定向就会停止 就我而言 重定向发生了多次 并且浏览器不遵循第二次重定向 遵循第一个重定向 但返回错误 我一直读到包含重
  • Spring Security with CAS 跳过会话固定保护

    我有一个使用 spring security 和 CAS spring 3 0 5 cas 3 4 5 的应用程序 但是当我登录时 会话 ID 没有改变 当我登录时CasAuthenticationFilter执行身份验证 如果身份验证成功
  • 使用 Spring Security 和 CAS 单点注销

    使用纯 Spring Java 配置 我在让 Spring 和 CAS 执行单点登录时遇到问题 我使用以下配置进行单点登录 我使用一个简单的 JSP 页面对 url 进行表单 POSThttps nginx shane com app lo
  • Java并发中的AbstractQueuedSynchronizer

    What is AbstractQueuedSynchronizer在Java中concurrent locks包用来做什么 有人可以阐明它的方法吗doAcquireInterruptibly and parkAndCheckInterru
  • 变量的同步和本地副本

    我正在查看一些具有以下习惯用法的遗留代码 Map
  • 我们是否需要使用 MappedByteBuffer.force() 将数据刷新到磁盘?

    我正在使用 MappedByteBuffer 来加速文件读 写操作 我的问题如下 我不确定是否需要使用 force 方法将内容刷新到磁盘 似乎没有 force getInt 仍然可以完美工作 好吧 因为这是一个内存映射缓冲区 我假设 get

随机推荐

  • AQS相关工实现类的使用及其原理

    文章目录 1 AQS 1 1 概述 1 2 自定义不可重入锁 2 ReentrantLock 2 1 非公平锁 2 1 1 加锁解锁流程 2 1 1 1 加锁失败 2 1 1 2 解锁竞争 2 2 可重入原理 2 3 可打断原理 2 3 1
  • 虚拟乒乓球连接不上服务器,虚拟乒乓球正版

    虚拟乒乓球正版 游戏画面场景设定的比较小清新 不过其中的内容设定是超级的精彩 极其逼真的玩家操作定能带给各位最为真实的游戏体验 这个过程你需要不断锻炼自己的水平 更得要击败尽可能多的对手 玩法难度可供选择 喜欢的玩家快快点击下载试玩吧 游戏
  • 解决jdbc连接本地mysql数据库时报错Caused by: java.net.UnknownHostException: mysql

    今天在写代码的时候遇到的问题 解决问题后记录下 The last packet sent successfully to the server was 0 milliseconds ago The driver has not receiv
  • Mali GPU OpenGL ES 应用性能优化--测试+定位+优化流程

    1 使用DS 5 Streamline定位瓶颈 DS 5 Streamline要求GPU驱动启用性能测试 在Mali GPU驱动中激活性能测试对性能影响微不足道 1 1 DS 5 Streamline简介 可使用DS 5 Streamlin
  • 解决VS中scanf()函数报错问题的四种方案(详细)

    scanf函数在VS中报错的主要原因是 scanf被认为不安全而被编译器默认设置为禁用 那么如何解决这个问题呢 法一 仅将函数scanf替换为scanf s即可 其他语法不变 但scanf s函数并不是C语言函数库里的标准函数 而是VS编译
  • Android中显示网页的多种方式

    在android中显示页面主要有两种方式 一种是在Activity里面直接显示网页 另一种是调用浏览器显示网页 方式不同 使用的方法也不同 下面我们分别讲解 一 在Activity里面直接显示网页 1 在Manifest xml文件里添加I
  • Ubuntu 安装anaconda后,自动进入base虚拟环境解决

    Ubuntu关闭anaconda自动进入base虚拟环境 在Ubuntu上安装完anaconda后 发现每次打开终端后都会自动进入到base的虚拟环境中去 虽然在这些环境下使用问题不大 但一些软件的安装在虚拟环境下有影响 每次使用conda
  • juc并发包整理

    目录 JUC提供了java并发编程需要的类 主要分几个大模块 1 原子类操作 2 锁 3 阻塞队列 4 并发集合 5 同步器 6 线程池 7组合式异步编程 JUC的作者Doug Lea神一样的人物 其中以上很多类的实现底层实现都是基于AQS
  • QPainter绘图工具的完善

    上一篇 QPainter实现简单的绘图程序 绘图工具 文章目录 前言 撤回功能的理解 拆分的理解 一 重绘函数的写法 二 绘制判断 三 橡皮擦 感谢各位的观看 前言 gitee工程地址 PaintTool 03 学习了简单的绘图工具后 程序
  • qt中菜单栏中添加快捷键

    使用技巧 在编辑好的qt的菜单中添加快捷键 具体添加菜单栏 可以参考博客 qt中菜单栏中实现第一个简单的打开功能 Littlehero 121的博客 CSDN博客 qt菜单栏打开文件 然后就是找到这个 或者是找到这个 双击动作 开始进行编辑
  • 算法提高 彩票 我只是觉得我的代码比较帅

    算法提高 彩票 时间限制 1 0s 内存限制 256 0MB 提交此题 问题描述 为丰富男生节活动 贵系女生设置彩票抽奖环节 规则如下 1 每张彩票上印有7个各不相同的号码 且这些号码的取值范围为 1 33 2 每次在兑奖前都会公布一个由七
  • Java基础-对象序列化

    对象序列化 作用 以内存为基准 把内存中的对象存储到磁盘文件中去 称为对象序列化 使用到的流是对象字节输出流 ObjectOutputStream package per mjn serializable import java io Se
  • Ubuntu下漏洞的修复流程

    最近需要修复cve漏洞 研究了如何在源码上修复漏洞 在这里记录一下 目录 I 介绍 漏洞和补丁 CVE漏洞 普通漏洞和CVE漏洞的区别 II 获取补丁 III 应用补丁 常见的打补丁工具 打补丁的步骤 patch的用法 I 介绍 首先介绍一
  • 最优检索二叉树

    最优检索二叉树 最优检索二叉树 抛出问题 算法的基本解决思路 空隙 检索数据的平均时间 小结 最优二叉检索树的实现算法分析 关于优化函数的递推方程 复杂性估计 总结 最优检索二叉树 抛出问题 算法的基本解决思路 空隙 所谓的空隙也就是查找的
  • 第十五讲:神州交换机端口安全配置

    知识点 开启端口安全模式 设置端口最大安全数 端口绑定MAC地址 违规处理 锁定安全端口 MAC地址与IP的绑定 端口镜像 实验拓扑如下图所示 PC机 IP地址 掩码 MAC地址 端口 PC1 192 168 1 10 255 255 25
  • 信息隐藏——二值图像的信息隐藏

    二值图像的信息隐藏 实验目的 使用一个特定图像区域中黑像素的个数来编码秘密信息 若某块P1 Bi gt 50 则嵌入一个1 若P0 Bi gt 50 则嵌入一个0 在嵌入过程中 为达到希望的像素关系 需要对一些像素的颜色进行调整 实验内容
  • [论文阅读] (06) 万字详解什么是生成对抗网络GAN?经典论文及案例普及

    娜璋带你读论文 系列主要是督促自己阅读优秀论文及听取学术讲座 并分享给大家 希望您喜欢 由于作者的英文水平和学术能力不高 需要不断提升 所以还请大家批评指正 非常欢迎大家给我留言评论 学术路上期待与您前行 加油 前一篇文章分享了Pvop老师
  • iOS 3DTouch的小细节

    在App启动后 添加3DTouch的快捷入口 代码如下 NSMutableArray arrShortcutItem NSMutableArray UIApplication sharedApplication shortcutItems
  • C语言程序-打印九九乘法表

    一 问题描述 使用C语言实现打印九九乘法表程序 二 程序实现 代码如下 include
  • 悲观锁(Synchronized)和乐观锁(CAS)

    文章目录 悲观锁和乐观锁 Synchronized Synchronized使用 Synchronized底层原理 Java1 6对Synchronized的优化 synchronized的等待唤醒机制 CAS CAS使用 CAS底层原理