Synchronized 的特性:
-
synchronized可以保证原子性
被 synchronized加锁的代码块只允许拿到锁的线程进入同步代码块/方法内部进行操作,也就是原子性的操作,以达到并发安全的效果.
-
synchronized可以保证有序性
基于synchronized的原子性,导致线程的互斥, 就能够保证
(这里注意,volatile保证的有序性和 synchronized保证的有序性是不同的,volatile 保证的有序性指的是禁止指令重排序而保证代码的有序性, synchronized保证的有序性指的是线程因为锁互斥的原因而保证之间的有序进行,因此就不会发生指令重排序这种情况, 因为这种情况发生在多线程同时修改的情况下.volatile详情点击这篇博客)
-
synchronized的特点:
-
既是乐观锁, 也是悲观锁
-
既是轻量级锁, 也是重量级锁
-
轻量级锁大概率基于自旋实现, 重量级锁基于挂起等待实现,
-
不是读写锁,
-
是可重入锁
-
是非公平锁
-
Synchronized 的使用
-
synchronized可以修饰代码块
synchronized修饰代码块的时候相当于这块代码块就是一个同步的,保证这个代码块是一个原子性的,只允许一个线程在代码块内部操作.
可以创建多种锁对象来对代码块进行加锁
- 对实例本身作为锁来进行加锁操作.
public class Blog_synchronized { public static void main(String[] args) { Blog_synchronized blog = new Blog_synchronized(); Thread t1 = new Thread(() -> { blog.test1(); },"t1"); Thread t2 = new Thread(() -> { blog.test1(); },"t2"); t1.start(); t2.start(); } public void test1() { //1. 修饰代码块 // 保证只有t2 调用该语句 if (Thread.currentThread().getName().equals("t2")) { System.out.println("t2在外部等待t1内部休眠!"); } synchronized (this) { System.out.println(Thread.currentThread().getName() + " 调用了 test1! " ); // 休眠10秒 try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
创建一个专用的锁对象,拿到锁对象的线程才可以进入同步代码块内
public class Blog_synchronized { // 锁对象 static final Object locker = new Object(); public static void main(String[] args) { Blog_synchronized blog = new Blog_synchronized(); Thread t1 = new Thread(() -> { blog.test1(); },"t1"); Thread t2 = new Thread(() -> { blog.test1(); },"t2"); t1.start(); t2.start(); } public void test1() { //1. 修饰代码块 // 保证只有t2 调用该语句 if (Thread.currentThread().getName().equals("t2")) { System.out.println("t2在外部等待t1内部休眠!"); } synchronized (locker) { System.out.println(Thread.currentThread().getName() + " 调用了 test1! " ); // 休眠10秒 try { System.out.println(Thread.currentThread().getName() +"休眠10秒" + "之后再释放locker"); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
- 对类对象加锁
因为一个类只能由一个类对象(唯一), 也就是可以通过getClass()方法查看获取的类对象( 类.class ), 因此我们可以将类对象作为锁对象来进行加锁,
public class Blog_synchronized {
public static void main(String[] args) throws InterruptedException {
Blog_synchronized blog = new Blog_synchronized();
Thread t1 = new Thread(() -> {
blog.test3();
},"t1");
Thread t2 = new Thread(() -> {
blog.test3();
},"t2");
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("t2 状态: " + t2.getState());
}
public void test3(){
if (Thread.currentThread().getName().equals("t2")) {
System.out.println("t2在外部等待t1内部休眠!");
}
synchronized (Blog_synchronized.class) {
try {
System.out.println(Thread.currentThread().getName() + "调用了test3!进入同步代码块内部!");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
t2进入后…
- synchronized 可以修饰方法
当synchronized修饰方法的时候其实等价于 synchronized(this) 因为只有能抢到实例对象的线程才可以调用该方法
public class Blog_synchronized {
public static void main(String[] args) throws InterruptedException {
Blog_synchronized blog = new Blog_synchronized();
Thread t1 = new Thread(() -> {
blog.test2();
},"t1");
Thread t2 = new Thread(() -> {
blog.test2();
},"t2");
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(t2.getState());
}
synchronized public void test2() {
System.out.println(Thread.currentThread().getName() + "调用了test2!");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当t1 占用该方法的时候 , 此时t2就是再不断自旋尝试获取加锁方法, 因此就是一个Blocked阻塞态
t1 释放了锁之后…
Synchronized的优化锁机制
锁膨胀/ 锁升级
体现了synchronized 能够"自适应" 能力
锁粗化
锁的粗细就指的是"锁的粒度"(加锁代码涉及范围,加锁代码的范围越大,锁的粒度越粗 ,加锁代码范围越小,锁的粒度越细)
也就是锁粒度越细 优点: 说明多个线程的并发性就更高,
如果锁粒度比较粗 优点: 就说明加锁解锁的开销就更小,
因此编译器就会有一个优化 自动判定 如果某个代码锁的粒度太细, 就会进行粗化 ( 扩大加锁代码的范围 )
如果两次加锁之间的间隔较大,(中间隔的代码多) ,一般不会优化,
如果两次加锁之间的间隔较小(中间隔的代码少) , 就很有可能出发这个优化.
锁消除
有些代码,明明不用加锁 , 但是你还是加锁了,编译器就会发现加锁好像没有必要, 此时就直接把锁给去掉了.