Synchroized
synchroized
代码示例:
package juc_1;/*
*@author wupeng
*@time 2021/7/14-14:02
synchronized即保证了可见性有保证了原子性
*/
public class T4 implements Runnable{
private int count = 100;
@Override
public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName()+"---- count + "+count);
}
public static void main(String[] args) {
T4 t = new T4();
for (int i = 0; i < 100; i++) {
new Thread(t, " Thread " + i).start();
}
}
}
验证同步非同步方法发之间的调用:
package juc_1;/*
*@author wupeng
*@time 2021/7/14-14:09
验证同步方法和非同步方法相互之间是否可以调用。
实验结果:可以相互调用
*/
public class T5 {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName()+"m1 start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"m1 end");
}
public void m2() {
System.out.println(Thread.currentThread().getName()+"m2 start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"m2");
}
public static void main(String[] args) {
T5 t = new T5();
new Thread(t::m2,"t2 ").start();
new Thread(t::m1,"t1 ").start();
}
}
银行账户,解决脏读问题:
package juc_1;/*
*@author wupeng
*@time 2021/7/14-14:16
模拟银行账户
对写进行加锁
对读不加锁
容易产生脏读问题
如果对读加上synchronized锁,就会解决脏读问题。
*/
import java.util.concurrent.TimeUnit;
public class T6 {
String name;
double balance;
public synchronized void set(String name,double balance) {
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
T6 t = new T6();
new Thread(()->t.set("zhangsan",100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getBalance("zhangsan"));
}
}
锁的可重入现象:
package juc_1;/*
*@author wupeng
*@time 2021/7/14-14:26
一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候任然会得到该对象的锁,这既是锁的可重入
*/
import java.util.concurrent.TimeUnit;
public class T7 {
synchronized void m1() {
System.out.println(Thread.currentThread().getName()+"m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
System.out.println("m1 end");
}
synchronized void m2() {
System.out.println("m2 start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2 end");
}
public static void main(String[] args) {
new T7().m1();
}
}
父子可重入锁:
package juc_1;/*
*@author wupeng
*@time 2021/7/14-14:34
父类synchronized,子类调用super的时候的时候必须是可重入锁。否则会出现问题。
*/
import java.util.concurrent.TimeUnit;
public class T8 {
synchronized void m() {
System.out.println("m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new TT8().m();
}
}
class TT8 extends T8 {
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
异常锁:
package juc_1;/*
*@author wupeng
*@time 2021/7/14-14:38
产生异常,就会被原来准备拿到这把锁的程序乱入进来
程序在执行过程中,出现异常默认锁被释放
*/
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class T9 {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName()+"start");
while (true) {
count++;
System.out.println(Thread.currentThread().getName()+"count = " +count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1/0; //此处出先异常,锁被释放。如果不想锁被释放,可以catch
System.out.println(i);
}
}
}
public static void main(String[] args) {
T9 t = new T9();
Runnable r = new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r,"t1").start();
new Thread(r,"t2").start();
}
}
Sybchroized的具体表现形式:
- 对于普通方法,Sybchroized锁的是当前实例对象
- 对于静态同步方法,Sybchroized锁的是当前类的Class对象
- 对于同步方法快,锁的是Sybchroized括号里配置的对象
JVM实现原理:
在JVM中,是基于进入和退出Monitor对象来实现方法同步和代码块的同步,但是两者的实现方式不一样,
同步代码快是使用monitorenter和monitorexit指令实现的,而方法的同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。
monitorenter指令,是在编译后插入到同步代码快的开始位置,而monitorexit是插入到方法的结束和异常处,JVM要保证每个monitorenter必须有对应的monitoorexit与之匹配。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的moniter的所有权,即尝试获得对象的锁。
synchroized底层实现:
加锁:访问某一段代码的临界资源得到时候需要有一把锁的锁住,
JDK早起的实现是重量级的,synchronized需要找操作系统申请锁,造成Synchronized效率低下。
后来的改进:
锁升级的概念
1.5之后对synchronized进行了改进,
sync(Object) 先在Object的头上markword 记录这个线程的ID (偏向锁)
如果有线程竞争使用的话,升级为自旋锁(默认旋10次),
如果10次之后还拿不到锁,升级为重量级锁。从操作系统拿OS锁(其实不占CPU的)。
锁只能升级,不能降级
执行时间长的用系统锁,实行时间短的,线程数比较少用自旋锁。
升级过程:
无状态锁->偏向锁升->自旋锁(轻量级锁)->升级为重量级锁