悲观锁和乐观锁
悲观锁
synchronized关键字和Lock的实现类都是悲观锁。
它很悲观,认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会一不做二不休的先加锁,确保数据不会被别的线程修改。
适合写操作多的场景,先加锁可以保证写操作时数据正确。
实例:
乐观锁
它很乐观,认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。
在Java中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果这个数据己经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。
乐观锁的实现方式:
-
采用Version版本号机制
-
采用CAS算法实现
实例:
锁案例演示
案例1
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){System.out.println("--------sendEmail");}public synchronized void sendSMS(){System.out.println("--------sendSMS");}
}
请问先打印邮件还是短信?
案例2
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public synchronized void sendSMS(){System.out.println("--------sendSMS");}
}
请问先打印邮件还是短信?
案例3
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.hello();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public void hello(){System.out.println("--------hello");}
}
请问先打印邮件还是hello?
案例4
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone2.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public synchronized void sendSMS(){System.out.println("--------sendSMS");}
}
有两部手机,请问先打印邮件还是短信?
案例5
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public static synchronized void sendSMS(){System.out.println("--------sendSMS");}
}
有两个静态同步方法,有一部手机,请问先打印邮件还是短信?
案例6
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone2.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public static synchronized void sendSMS(){System.out.println("--------sendSMS");}
}
有两个静态同步方法,有两部手机,请问先打印邮件还是短信?
案例7
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();} System.out.println("--------sendEmail");}public static void sendSMS(){System.out.println("--------sendSMS");}
}
有一个静态同步方法,一个普通静态方法,有一部手机,请问先打印邮件还是短信?
案例8
import java.util.concurrent.TimeUnit;public class JUC04 {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();new Thread(()->{phone2.sendSMS();},"b").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}
}/*** 资源类*/
class Phone{public static synchronized void sendEmail(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------sendEmail");}public static void sendSMS(){System.out.println("--------sendSMS");}
}
有一个静态同步方法,一个普通静态方法,有两部手机,请问先打印邮件还是短信?
笔记总结
案例1和案例2:
一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法,锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的 synchronized方法。
案例3和案例4:
普通方法和同步锁无关,换成两个对象后,不是同一把锁了
案例5和案例6:
对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部手机,所有的普通同步方法用的都是同一把锁->实例对象本身;
对于静态同步方法,锁的是当前类的class 对象,如Phone.class;
对于同步代码块,锁的是 synchronized 扩号内的对象。
案例7和案例8:
当一个线程试图访问同步代码块时,它首先必须得到锁,正常退出或抛出异常时必须释放锁。
所有的普通同步方法用的都是同一把锁-实例对象本身,就是new出来的具体实例对象本身,本类this
也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能再次获取锁。
所有的静态同步方法用的也是同一把锁-类对象(类名.class)本身,具体实例对象this 和类对象本身,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的,但是一但一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。
synchronized字节码分析
synchronized代码块
public class JUC05 {Object o=new Object();public void m1(){synchronized (o){System.out.println("----hello synchronized code block");}}public static void main(String[] args) {}
}
运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class
,右键->打开于->终端,在终端中输入javap -c JUC05
,对 JUC05.class
进行反编译,得到如下信息:
synchronized代码块使用的是monitorenter和monitorexit指令来持有锁和释放锁。
问: 一定是1个monitorenter对应2个monitorexit吗?
答: 一般情况下,1个monitorenter对应2个monitorexit,但是也存在极端的情况,1个monitorenter对应1个monitorexit
synchronized同步方法
public class JUC05 {public synchronized void m2(){System.out.println("----hello synchronized m2");}public static void main(String[] args) {}
}
运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class
,右键->打开于->终端,在终端中输入javap -v JUC05
,对 JUC05.class
进行反编译,得到如下信息:
调用指令将会检查方法的ACC_SYNCHRONIZED
访问标志是否被设置。如果设置了,执行线程会先持有montor锁, 然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor
synchronized静态同步方法
public class JUC05 {public synchronized void m2(){System.out.println("----hello synchronized m2");}public static synchronized void m3(){System.out.println("----hello static synchronized m3");}public static void main(String[] args) {}
}
运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class
,右键->打开于->终端,在终端中输入javap -v JUC05
,对 JUC05.class
进行反编译,得到如下信息:
ACC_STATIC, ACC_SYNCHRONIZED访问标识来区分该方法是否静态同步方法
synchronized底层原语分析
问: 为什么任何一个对象都可以成为一个锁?
答:
公平锁和非公平锁
公平锁
非公平锁
问: 为什么会有公平锁和非公平锁的设计?为什么默认非公平?
答:
-
恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。
-
使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
**问:**什么时候用非公平锁,什么时候用公平锁?
答:如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则那就用公平锁,大家公平使用。
可重入锁(递归锁)
定义
可:可以;重:再次;入:进入;锁:同步锁。