synchronized 异常_由浅入深,Java 并发编程中的 Synchronized

synchronized 作用

synchronized 关键字是 Java 并发编程中线程同步的常用手段之一。

1.1 作用:

  • 确保线程互斥的访问同步代,锁自动释放,多个线程操作同个代码块或函数必须排队获得锁,
  • 保证共享变量的修改能够及时可见,获得锁的线程操作完毕后会将所数据刷新到共享内存区;
  • 不解决重排序,但保证有序性。

1.2 用法:

  • 修饰实例方法synchronized 关键词作用在方法的前面,用来锁定方法,其实默认锁定的是 this 对象。
public class Thread1 implements Runnable{    //共享资源(临界资源)    static int i=0;    //如果没有synchronized关键字,输出小于20000    public synchronized void increase(){        i++;    }    public void run() {        for(int j=0;j<10000;j++){            increase();        }    }    public static void main(String[] args) throws InterruptedException {        Thread1 t=new Thread1();        Thread t1=new Thread(t);        Thread t2=new Thread(t);        t1.start();        t2.start();        t1.join();//主线程等待t1执行完毕        t2.join();//主线程等待t2执行完毕        System.out.println(i);    }}
44a33bc30e412c52c3dd8a052fd148df.png
  • 修饰静态方法synchronized 还是修饰在方法上,不过修饰的是静态方法,等价于锁定的是 Class 对象。
 public class Thread1 {    //共享资源(临界资源)    static int i = 0;    //如果没有synchronized关键字,输出小于20000    public static synchronized void increase() {        i++;    }    public static void main(String[] args) throws InterruptedException {        Thread t1 = new Thread(new Runnable() {            public void run() {                for (int j = 0; j < 10000; j++) {                    increase();                }            }        });        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {                for (int j = 0; j < 10000; j++) {                    increase();                }            }        });        t1.start();        t2.start();        t1.join();//主线程等待t1执行完毕        t2.join();//主线程等待t2执行完毕        System.out.println(i);    }}
bf81ffb4e5a9688bd47759b4a824ab54.png
  • 修饰代码块用法是在函数体内部对于要修改的参数区间用 synchronized 来修饰,相比与锁定函数这个范围更小,可以指定锁定什么对象。
public class Thread1 implements Runnable {    //共享资源(临界资源)    static int i = 0;    @Override    public void run() {        for (int j = 0; j < 10000; j++) {            //获得了String的类锁            synchronized (String.class) {                i++;            }        }    }    public static void main(String[] args) throws InterruptedException {        Thread1 t = new Thread1();        Thread t1 = new Thread(t);        Thread t2 = new Thread(t);        t1.start();        t2.start();        t1.join();        t2.join();        System.out.println(i);    }}
a1a3ea7d6aa4590a051be92a13c4d717.png

总结:

  1. synchronized 修饰的实例方法,多线程并发访问时,只能有一个线程进入,获得对象内置锁,其他线程阻塞等待,但在此期间线程仍然可以访问其他方法。
synchronized 修饰的静态方法,多线程并发访问时,只能有一个线程进入,获得类锁,其他线程阻塞等待,但在此期间线程仍然可以访问其他方法。synchronized 修饰的代码块,多线程并发访问时,只能有一个线程进入,根据括号中的对象或者是类,获得相应的对象内置锁或者是类锁每个类都有一个类锁,类的每个对象也有一个内置锁,它们是互不干扰的,也就是说一个线程可以同时获得类锁和该类实例化对象的内置锁,当线程访问非 synchronzied 修饰的方法时,并不需要获得锁,因此不会产生阻塞。

管程

管程(英语:Monitors,也称为监视器) 在操作系统中是很重要的概念,管程其实指的是管理共享变量以及管理共享变量的操作过程。有点扮演中介的意思,管程管理一堆对象,多个线程同一时候只能有一个线程来访问这些东西。

管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

进程只能互斥地使用管程,即当一个进程使用管程时,另一个进程必须等待。当一个进程使用完管程后,它必须释放管程并唤醒等待管程的某一个进程。

管程解决互斥问题相对简单,需要把共享变量以及共享变量的操作都封装在一个类中。

18028de76ef548f408c6b478cd9700d4.png

当线程 A 和线程 B 需要获取共享变量 count 时,就需要调用 get 和 set 方法,而 get 和 set 方法则保证互斥性,保证每次只能有一个线程访问。

生活中举例管程,比如链家店长分配给每个中介管理一部分二手房源,多个客户通过中介进行房屋买卖。

  • 中介就是管程。
  • 多个二手房源被一个中介管理中,就是一个管程管理着多个系统资源。
  • 多个客户就相当于多个线程。

Synchronzied 的底层原理

对象头解析

我们知道在 Java 的 JVM 内存区域中一个对象在堆区创建,创建后的对象由对象头、实例变量、填充数据三部分组成。这三部分功能如下:

  1. 填充数据:由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
实例变量:存放类的属性数据信息,包括父类的属性信息,这部分内存按 4 字节对齐。对象头:主要包括两部分 Klass Point 跟 Mark Word。
a7a6264dc51edba838e1511f9ec02b16.png

Klass Point (类型指针):是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

Mark Word (标记字段):这一部分用于储存对象自身的运行时的数据,如哈希码、GC 分代年龄、锁状态标志、锁指针等。这部分数据在 32 bit 和 64 bit 的虚拟机中大小分别为 32 bit 和 64 bit,考虑到虚拟机的空间效率,Mark Word 被设计成一个非固定的数据结构,以便在极小的空间中存储尽量多的信息,它会根据对象的状态复用自己的存储空间(跟 ConcurrentHashMap 里的标志位类似),详细情况如下图:

bb1dab3fab083362ed6ebba307a20938.png

synchronized 不论是修饰方法还是代码块,都是通过持有修饰对象的锁来实现同步,synchronized 锁对象是存在对象头 Mark Word。

其中,轻量级锁和偏向锁是 Java6 对 synchronized 锁进行优化后新增加的。这里我们主要分析一下重量级锁,也就是通常说 synchronized 的对象锁。所标识位为 10,其中指针指向的是 monitor 对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联。

d0bc0345183b6b56403e8ba6736c3fae.png

反汇编查看

分析对象的 monitor 前我们先通过反汇编看下同步方法跟同步方法块在汇编语言级别是什么样的指令。

public class SynchronizedTest {    public synchronized void doSth(){        System.out.println("Hello World");    }    public void doSth1(){        synchronized (SynchronizedTest.class){            System.out.println("Hello World");        }    }}

javac SynchronizedTest .java 然后 javap -c SynchronizedTest 反编译后看汇编指令如下:

 public synchronized void doSth();    descriptor: ()V    flags: ACC_PUBLIC, ACC_SYNCHRONIZED //  这是重点 方法锁    Code:      stack=2, locals=1, args_size=1         0: getstatic     #2                           3: ldc           #3             5: invokevirtual #4                           8: return  public void doSth1();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=2, locals=3, args_size=1         0: ldc           #5                          2: dup         3: astore_1         4: monitorenter  //   进入同步方法         5: getstatic     #2                           8: ldc           #3                          10: invokevirtual #4                        13: aload_1        14: monitorexit  //正常时 退出同步方法        15: goto          23        18: astore_2        19: aload_1        20: monitorexit  // 异常时 退出同步方法        21: aload_2        22: athrow        23: return

我们可以看到 Java 编译器为我们生成的字节码。在对于 doSth 和 doSth1 的处理上稍有不同。也就是说,JVM 对于同步方法和同步代码块的处理方式不同。对于同步方法,JVM 采用ACC_SYNCHRONIZED 标记符来实现同步。对于同步代码块。JVM 采用 monitorenter、monitorexit 两个指令来实现同步。

ACC_SYNCHRONIZED

方法级的同步是隐式的。

同步方法的常量池中会有一个 ACC_SYNCHRONIZED 标志。当某个线程要访问某个方法的时候,会检查是否有 ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。

这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

monitorenter 跟 monitorexit

可以把执行 monitorenter 指令理解为加锁,执行 monitorexit 理解为释放锁。

每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0,当一个线程获得锁(执行 monitorenter )后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行 monitorexit 指令)的时候,计数器再自检。

当计数器为 0 的时候。锁将被释放,其他线程便可以获得锁。

结论:

同步方法和同步代码块底层都是通过 monitor 来实现同步的。两者区别:同步方式是通过方法中的 access_flags 中设置 ACC_SYNCHRONIZED 标志来实现,同步代码块是通过 monitorenter 和 monitorexit 来实现。

monitor 解析

每个对象都与一个 monitor 相关联,而 monitor 可以被线程拥有或释放,在Java 虚拟机( HotSpot )中,monitor 是由 ObjectMonitor 实现的,其主要数据结构如下(位于 HotSpot 虚拟机源码 ObjectMonitor.hpp 文件,C++实现的)。

ObjectMonitor() {    _count        = 0;      //记录数    _recursions   = 0;      //锁的重入次数    _owner        = NULL;   //指向持有ObjectMonitor对象的线程     _WaitSet      = NULL;   //调用wait后,线程会被加入到_WaitSet    _EntryList    = NULL ;  //等待获取锁的线程,会被加入到该列表}

monitor 运行图如下:

ce8e12f6dda772f4a74ab45f39423c63.png

对于一个 synchronized 修饰的方法(代码块)来说:

  • 当多个线程同时访问该方法,那么这些线程会先被放进_EntryList 队列,此时线程处于 blocked 状态;
  • 当一个线程获取到了对象的 monitor 后,那么就可以进入 running 状态,执行方法块,此时,ObjectMonitor 对象的_owner 指向当前线程,_count 加 1 表示当前对象锁被一个线程获取;
  • 当 running 状态的线程调用 wait() 方法,那么当前线程释放 monitor 对象,进入 waiting 状态,ObjectMonitor 对象的_owner 变为 null,_count 见 1,同时线程进入_WaitSet 队列,直到有线程调用 notify() 方法唤醒该线程,则该线程进入_EntryList 队列,竞争到锁再进入_owner区;
  • 如果当前线程执行完毕,那么也释放 monitor 对象,ObjectMonitor 对象的_owner 变为 null,_count 见 1。

因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态(具体可看CXUAN 写的 OS 哦),这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是早期的 synchronized 效率低的原因。庆幸在 Java 6 之后Java 官方对从 JVM 层面对 synchronized 较大优化最终提升显著,Java 6 之后,为了减少获得锁和释放锁所带来的性能消耗,引入了锁升级的概念。

锁升级

synchronized 锁有四种状态:无锁、偏向锁、轻量级锁、重量级锁。

这几个状态会随着竞争状态逐渐升级,锁可以升级但不能降级,但是偏向锁状态可以被重置为无锁状态。科学性的说这些锁之前我们先看个简单通俗的例子来加深印象。

通俗说法理解各种锁

偏向锁、轻量级锁和重量级锁之间的关系,首先打个比方:假设现在厕所只有一个位置,每个使用者都有打开门锁的钥匙。必须打开门锁才能使用厕所。其中小明、小红理解为两个线程,上厕所理解为执行同步代码,门锁理解为同步代码的锁

  • 小明今天吃坏了东西需要反复去厕所,如果小明每次都要开锁就很耽误时间,于是门锁将小明的脸记录下来(假设那个锁是智能锁),下次小明再来的时候门锁会自动识别出是小明来了,然后自动开锁,这样就省去了小明拿钥匙开门的过程,此时门锁就是偏向锁,也可以理解为偏向小明的锁。
接下来,小红又去上厕所,试图将厕所的门锁设置为偏向自己的偏向锁,于是发现门锁无法偏向自己,因为此时门锁已是偏向小明的偏向锁。于是小红很生气,要求门锁撤销对小明的偏向,当然,小明也不同意门锁偏向小红。于是等小明用完厕所之后,门锁撤销了对任何人的偏向(只要出现竞争的情况,就会撤销偏向锁)。这个过程就是撤销偏向锁。此时门锁升级为轻量级锁。等小明出来以后,轻量级锁正式生效 。下一次小明和小红同时来厕所,谁跑的快谁先走到门前,开门后将门锁拿进厕所,并将门锁打开以后拿进厕所里,将门反锁,于是在门外原来放门锁的位置放置了一个有人的标志(这个标识可以理解为指向门锁的指针,或者理解为作为锁的 Java 对象头的 Mark Word 值),这时,小红看到有人以后很着急,催着里面的人出来时马上进去,于是不断的来敲门,问小明什么时候出来。这个过程就是自旋。反复敲了几次以后,小明受不了了,对小红喊话,说你别敲了,等我用完厕所我告诉你,于是小红去一边等着(线性阻塞)。此时门锁升级为重量级锁。升级为重量级锁的后果就是,小红不再反复敲门,小明在上完厕所以后必须告诉小红一声,否则小红就会一直等着。

结论:

偏向锁在只有一个人上厕所时非常高效,省去了开门的过程。

轻量级锁在有多人上厕所但是每个人使用的特别快的时候,比较高效,因为会出现这种现象,小红敲门的时候正好赶上小明出来,这样就省得小明出来告诉小红以后小红才能进去,但是这样可能会出现小红敲门失败的情况(就是敲门时小明还没用完)。

重量级锁相比与轻量级锁的多了一步小明呼唤小红的步骤,但是却省掉了小红反复去敲门的过程,但是能保证小红去厕所时厕所一定是没人的。

偏向锁

经过 HotSpot 作者大量的研究发现:大多数时候是不存在锁竞争的,经常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。核心思想:

如果一个线程获得了锁,那么锁就进入偏向模式,此时 Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程。

这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的。

因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。

具体流程:当线程 1 访问代码块并获取锁对象时,会在 java 对象头和栈帧中记录偏向的锁的 threadID,因为偏向锁不会主动释放锁。因此以后线程 1 再次获取锁的时候,需要比较当前线程的 threadID 和 Java 对象头中的threadID 是否一致,如果一致(还是线程 1 获取锁对象),则无需使用 CAS 来加锁、解锁;如果不一致(其他线程,如线程 2 要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程 1 的 threadID),那么需要查看Java 对象头中记录的线程 1 是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程 2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程 1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程 1,撤销偏向锁,升级为 轻量级锁,如果线程 1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

轻量级锁

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要高昂的耗时实现 CPU 从用户态转到内核态的切换,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

原理跟升级:线程 1 获取轻量级锁时会先把锁对象的对象头 MarkWord 复制一份到线程 1 的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord ),然后使用 CAS 把对象头中的内容替换为线程 1 存储的锁记录(DisplacedMarkWord)的地址;

如果在线程 1 复制对象头的同时(在线程 1 CAS 之前),线程 2 也准备获取锁,复制了对象头到线程 2 的锁记录空间中,但是在线程 2 CAS 的时候,发现线程 1 已经把对象头换了,「线程 2 的 CAS 失败,那么线程 2 就尝试使用自旋锁来等待线程 1 释放锁」。自旋锁简单来说就是让线程 2 在循环中不断 CAS 尝试获得锁对象。

但是如果自旋的时间太长也不行,因为自旋是要消耗 CPU 的,因此自旋的次数是有限制的。比如 10 次或者 100 次,如果自旋次数到了线程 1 还没有释放锁,或者线程 1 还在执行,线程 2 还在自旋等待,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止 CPU 空转。

31f1021496b056d12a2ff8e50c32f7a0.png

锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java 虚拟机在 JIT 编译时通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消,除没有必要的锁,可以节省毫无意义的请求锁时间,我们知道StringBuffer 是线程安全的,里面包含锁的存在,但是如果我们在函数内部使用 StringBuffer 那么代码会在 JIT 后会自动将锁释放掉哦。

对比如下:

锁状态优点缺点适用场景偏向锁加锁解锁无需额外消耗,跟非同步方法时间相差纳秒级别如果竞争线程多,会带来额外的锁撤销的消耗基本没有其他线程竞争的同步场景轻量级锁竞争的线程不会阻塞而是在自旋,可提高程序响应速度如果一直无法获得会自旋消耗CPU少量线程竞争,持有锁时间不长,追求响应速度重量级锁线程竞争不会导致 CPU 自旋跟消耗 CPU 资源线程阻塞,响应时间长很多线程竞争锁,切锁持有时间长,追求吞吐量时候

PS:ReentrantLock 底层实现依赖于特殊的 CPU 指令,比如发送 lock 指令和 unlock 指令,不需要用户态和内核态的切换,所以效率高。而synchronized 底层由监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 需要用户态和内核态的切换,所以效率会低一些。

锁升级流程图

最后奉上 unbelievableme 绘制的锁升级大图。

036c689c087eeba5a24af776cc194f39.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/396521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux内存分配机制之伙伴系统和SLAB

转载请注明原文地址&#xff1a;http://www.cnblogs.com/ygj0930/p/6539590.html 内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下&#xff0c;避免碎片的产生。这就要求内核采取灵活而恰当的内存分配策略。通常&#xff0c;内存分配一般有两种情况&#xff1a…

this.$modal.confirm 自定义按钮关闭_自定义函数,让你玩转Excel得心应手

让“自动更正”输入统一的文本&#xff0c;你是不是经常为输入某些固定的文本,如《电脑报》而烦恼呢?那就往下看吧。1.执行“工具→自动更正”命令,打开“自动更正”对话框。2.在“替换”下面的方框中输入“pcw”(也可以是其他字符,“pcw”用小写),在“替换为”下面的方框中输…

CCF-CSP 最大的矩形

问题描述在横轴上放了n个相邻的矩形&#xff0c;每个矩形的宽度是1&#xff0c;而第i&#xff08;1 ≤ i ≤ n&#xff09;个矩形的高度是hi。这n个矩形构成了一个直方图。例如&#xff0c;下图中六个矩形的高度就分别是3, 1, 6, 5, 2, 3。请找出能放在给定直方图里面积最大的矩…

MySQL的主从复制云栖社区_MySQL-主从复制

前言前篇说了作为运维在数据库块最起码要会两大技能&#xff0c;今天来说说第二技能--主从复制随着业务的增长&#xff0c;一台数据库服务器以满足不了需求了&#xff0c;负载过重&#xff0c;这时候就需要减压&#xff0c;实现负载均衡读写分离&#xff0c;一主一从或一主多从…

mysql 数据分析的步骤_数据分析8个主要步骤

# 在对数据进行分析时&#xff0c;主要细分为明确目标、应用思维和如下8个具体步骤&#xff1a;1、读取数据2、清洗数据3、操作数据4、转换数据5、整理数据6、分析数据7、展现数据8、总结报告接下来将介绍使用python来具体处理数据&#xff0c;包括上面几个步骤的实现&#xff…

一、win7下安装yii2

作者&#xff1a;PHP学习网 出处&#xff1a;http://www.viphper.com/?p1159 本文版权归作者&#xff0c;欢迎转载&#xff0c;但未经作者同意必须保留此段声明&#xff0c;且在文章页面明显位置给出原文连接&#xff0c;否则保留追究法律责任的权利。 之前在liunx上安装过yii…

vs dll必须和exe在同一个目录_Win10系统丢失 .dll 文件的三种解决方案教程

有时候开机或打开一个软件时&#xff0c;系统会提示无法启动程序&#xff0c;这是怎么回事呢&#xff1f;这是因为计算机丢失某个或某些dll文件&#xff0c;由于系统本身不存在这些运行库文件&#xff0c;需要进行添加才能使用该软件。方法一&#xff1a;下载丢失的.dll文件&am…

datagrid页面获取表单一条数据的例子

【问题背景】 最近在做ITOO考评的时候想从页面获取表单选中的数据&#xff1a; 【代码】 在数据网格&#xff08;datagrid&#xff09;组件包含两种方法来检索选中行数据&#xff1a; getSelected&#xff1a;取得第一个选中行数据&#xff0c;如果没有选中行&#xff0c;则返回…

Spring+SpringMVC+MyBatis+easyUI整合基础篇(十)SVN搭建

前言 前面一篇文章讲了一下版本控制&#xff0c;但其实这一篇并没有打算讲细节的&#xff0c;感觉应该自己去动手弄一下&#xff0c;后来考虑了一下&#xff0c;版本控制真的挺重要的&#xff0c;如果自己实在搭建不好反而不去使用的话&#xff0c;真的有点可惜&#xff0c;当然…

串口数据波形显示_【专题教程第6期】SEGGER的J-Scope波形上位机软件,RTT模式波形上传可飙到500KB/S...

【专题教程第6期】SEGGER的J-Scope波形上位机软件&#xff0c;RTT模式波形上传速度可狂飙到500KB/S左右说明&#xff1a;1、在实际项目中&#xff0c;很多时候&#xff0c;我们需要将传感器或者ADC的数值以波形的形式显示。通常的解决办法是用串口上位机&#xff0c;USB接口上位…

JavaScript-基础入门.0014.JavaScript内置对象

2019独角兽企业重金招聘Python工程师标准>>> 简单介绍: 说明: 内置对象就是不依赖宿主环境在程序执行之前已经存在的对象,不必显式的实例化内置对象,前面已经介绍了大部分内置对象,而Global和Math两个单体内置对象比较特殊,所以单独讲. Global对象: 说明: Js中不属于…

Java中字符串连接符(+)和append的区别

参考链接&#xff1a;https://www.jianshu.com/p/849f1d443b3a 文章结构&#xff1a; 第一部分&#xff1a;对的原理进行分析 第二部分&#xff1a; 和 append 循环对比&#xff08;贴出代码&#xff0c;以及分别循环后的反编译字节码&#xff0c;来对两者进行对比&#xff09;…

百度seo排名点击器_SEO整站优化思路 - 百度seo排名点击

原出处&#xff1a;超级排名系统原文链接&#xff1a;SEO整站优化思路 - 超级排名系统整站优化的大体思路包括哪些&#xff1f;下面超级排名系统小编详细给大家讲解下&#xff1a;一、建站优化我们在建立网站之前与建立网站的时候需要做的优化&#xff0c;包括标题优化、描述关…

unity 天空盒_Unity自定义可编程渲染管线(SRP)(二)——编写第一个自定义SRP

一句话描述&#xff0c;我们可以把SRP分解成两个部分&#xff0c;分别是SRP Asset&#xff0c;SRP Instance。SRP AssetSRP Asset是一个Unity Asset文件&#xff0c;用来存储渲染管线的特定配置信息&#xff0c;包含的信息有&#xff1a;游戏物体是否应该投射阴影&#xff1b;使…

【design pattern】工厂方法模式和抽象工厂模式

前言 设计模式分为三大类&#xff1a; 创建型模式&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式&#xff1b; 结构型模式&#xff1a;适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式&#xff1b; 行为型模式&#xff1…

弹簧触摸开关原理图_10年老电工经验之谈:常见的开关电源那些事儿

随着电控系统中对控制回路安全性要求提高&#xff0c;控制回路的工作电压是越来越趋于直流低压化。由此&#xff0c;控制回路内各种使用直流电源的传感器、中小微型直流继电器等电器装置是日益增多。也正因如此&#xff0c;电控系统内不同品牌的开关电源也相应地多了起来。因牵…

erlang mysql连接超时_Erlang数据库-(一)Erlang与Mysql的连接

下载&#xff1a;http://download.csdn.net/detail/hjhjava/7088277把该文件的东西全部编译好&#xff0c;然后根据下面的解释就可以操作Mysql的。很简单~-record(row,{a1 0, a20}).%% 第一个参数是该链接的名字(根据它来操作Mysql)&#xff0c;第二参数是本机号(ip地址)%% &a…

管理活动目录域服务实训_管理学院学生党支部开展实践教育基地服务活动

红星E校有态度 有温度 可关注为进一步加强党的建设&#xff0c;深化管理学院学生党支部与实践基地的互动性&#xff0c;2020年9月11至12日&#xff0c;管理学院学生党支部协助白鹤村村委实践基地完成第七次人口普查相关工作&#xff0c;共计6名预备党员参与。工作开始前的培训会…

mysql 汉编码 的选_peewee连接mysql汉语言数据编码_mysql

peewee连接mysql中文数据编码系统是win7 x64python 2.7.6的site.py里面编码设定为 utf-8py文件首行指定 #coding:utf-8mysql 5.5.38安装时指定代码为utf-8peewee的连接数据库代码为&#xff1a;db MySQLDatabase(host 127.0.0.1, user root, passwd 1, database mz, chars…

配置windows失败,还原更新,请勿关机

最近给同事装系统&#xff0c;偶尔会出现如下问题&#xff1a; 如果是这种情况&#xff0c;只能耐心等待了&#xff0c;因为关机也没用&#xff01; 转载于:https://www.cnblogs.com/lijy/p/5327844.html