synchronized同步化
用于保证某一时刻上只有一个线程在执行任务,不会出现并发的情况,从而达到等待队列的执行和保证线程安全的目的。
synchronized的三种用法
修饰实例方法
- 只有一个线程进入了当前类对象的同步方法,同时不允许其他线程再次进入当前对象的任何同步方法,但是准许进入非同步方法。
- 当线程进入当前类对象的其他同步方法(重入),也允许进入非同步方法。当前线程进入同步方法,则获取同步锁,结束同步方法时也会自动释放锁。
public class Account {
private Long Id;//编号
private double money;//金额
public synchronized void draw(double num){
if(this.money > money){
System.out.println("正在取钱,请稍后");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money-=money;
}
}
public synchronized void deposit(double money){
double d = this.money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
d+=money;
this.money = d;
}
}
该操作中,对当前对象中的所有方法都是互斥的,而且会影响并发性
修饰静态方法
作用于当前类对象加锁,进入同步静态方法需要获取当前类对象的锁。
public class Num {
private static int num = 0;
public static synchronized void add() {
int i = num;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
num = i;
System.out.println("num:" + i);
}
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
Num num = new Num();
num.add();
//构建10个Num对象,如果都使用同步方法则不能达到上锁的目的
});
threads[i].start();;
}
for (Thread t:threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Num.num);
}
}
}
如果去掉方法上的static,则不能实现加锁的效果,因为没有static时,则变成了对象锁,若创建多个对象,各个对象之间没有任何关系,所以不能达到锁定的效果,但是如果添加static,则变为类锁,类只有一个,所以可以达到互斥锁的效果
修饰代码块
//写法一
public void draw(double num){
synchronized (this){//等价于public synchronized void draw(double num)
if(this.money > money){
System.out.println("正在取钱,请稍后");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money-=money;
}
}
}
//写法二
public void draw(double num){
synchronized (Account.class){
if(this.money > money){
System.out.println("正在取钱,请稍后");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money-=money;
}
}
}
使用synchronized修饰代码块时,同时指定加锁对象,对给定对象加锁,进入同步代码块的前提是获取指定对象的锁,在可能发生并发访问的共享临界资源使用,通过这种方法可以保证并发线程在任何一个时刻只有线程可以进入修改共享的临界资源的代码块。
synchronized底层原理
同步代码块在源代码编译成字节码是,同步代码块部分是利用monitorenter和monitorexit这两个字节码指令实现的,它们分别位于同步代码块的起始和终止位置,当虚拟机执行到monitorenter时,当前线程会获取monitor对象的所有权,如果未加锁或者已经被当前线程持锁,就把锁的计数器+1,当执行monitorexit时,锁的计数器会-1,当计数器为0时,该锁就会被释放掉,如果获取monitor对象失败,则线程会进入阻塞窗台,直到其他线程将锁释放。
对象的内存布局
虚拟机创建对象是先申请内存,然后对内存进行数据填充,数据填充包括设置对象头,例如将OOP对应的K拉萨市属性作为初始值填充,如果使用了锁机制,还需填充对应的锁标志值。
锁的四种状态
无锁: 对象头开辟54bit空间存放对象的hashcode值,4bit用于存放分代年龄,1bit用于存放是否使用偏向。无锁表示没有对资源进行锁定,所有的线程都可以对同一资源进行访问,但是只能一个线程进行修改。
偏向锁: 适用于只有一个线程访问的同步场景。如果存在多线程竞争则会带来额外的锁撤销消耗,但是加锁和解锁消耗低。
轻量级锁: 适用于追求响应时间的场景,可能会出现始终无法获取资源,线程进行自旋操作,消耗CPU,但是可以提供响应速度。
重量级锁: 使用于追求高吞吐量的场景,得不到锁的线程会阻塞,性能比较差,但是阻塞是不会消耗CPU。