public class T1_Synchronized01 { private int count = 1; private Object o = new Object(); public void m1() { synchronized (o) { // 必须先拿到o这个锁 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } } }
二.synchronized基础用法
2.1 通过对象进行锁
在代码里,可以通过创建一个对象,这样要想拿到临界资源,就必须先获得到这个对象的锁。
public class T1_Synchronized01 { private int count = 1; private Object o = new Object(); // 堆代码 duidaima.com public void m1() { synchronized (o) { // 必须先拿到o这个锁 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } } }
2.2 通过this
使用this代表锁住的是当前对象,这种方法等同直接把synchronized关键字加在方法前。
public class T1_Synchronized01 { private int count = 1; public void m2() { synchronized (this) { // 必须先拿到this的锁 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } } public synchronized void m3() { // 与m2一样 count++; System.out.println(Thread.currentThread().getName() + " count = " + count); } }
2.3 锁定静态方法
锁定静态方法需要通过类.class,或者直接在静态方法上加上关键字。但是,类.class不能使用this来代替。注:在同一个类加载器中,class是单例的,这也就能保证synchronized能够只让一个线程访问临界资源。public class T1_Synchronized01 { public static void m4() { // 静态方法 synchronized (T1_Synchronized01.class) { System.out.println(Thread.currentThread().getName()); } } // 堆代码 duidaima.com public synchronized static void m5() { System.out.println(Thread.currentThread().getName()); } }
2.4 实验测试
①、首先测试一下,同步和非同步是否可以相互调用
定义两个线程,一个执行同步方法,一个执行非同步方法,如果不能够互相调用,那么,非同步线程就需要等待同步线程执行完之后在继续执行。public class T3_Synchronized03 { public synchronized void one() { System.out.println(Thread.currentThread().getName() + " start one method"); try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + " end one method"); } // 堆代码 duidaima.com public void two() { System.out.println(Thread.currentThread().getName() + " execute two method"); } public static void main(String[] args) { T3_Synchronized03 t = new T3_Synchronized03(); new Thread(t::one, "第一个线程").start(); new Thread(t::two, "第二个线程").start(); } }从运行的结果可以看出是可以的。
public class T4_Synchronized { private int ticket = 100; // 模拟100张票 public synchronized int getTicket() { // 读 return this.ticket; } public synchronized void buy(int number) { // 写 try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket = ticket - number; } public static void main(String[] args) throws InterruptedException { T4_Synchronized bus = new T4_Synchronized(); System.out.println("刚开始有票数:" + bus.getTicket()); new Thread(() -> bus.buy(1)).start(); for (int i = 1; i <= 10; i++) { Thread.sleep(500); int finalI = i; new Thread(() -> System.out.println("第" + finalI + "次查询余票数:" + bus.getTicket())).start(); } } }运行之后,我们可以发现,数据是正确的,尽管是在查询的时候并没有睡眠0.5秒,显示数据依然是期望数据。
public class T5_Synchronized { // 堆代码 duidaima.com synchronized void one() { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } two(); System.out.println("one - thread-" + Thread.currentThread().getName() + " end"); } synchronized void two() { try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("two - thread-" + Thread.currentThread().getName() + " end"); } public static void main(String[] args) { T5_Synchronized t5 = new T5_Synchronized(); new Thread(t5::one, "one1").start(); new Thread(t5::one, "one2").start(); new Thread(t5::one, "one3").start(); } }实验结果
public class T6_Synchronized { int count = 0; synchronized void add() { System.out.println("线程 " + Thread.currentThread().getName() + " start"); while (true) { count++; System.out.println("线程 " + Thread.currentThread().getName() + " now count = " + count); if (count == 3) { throw new NullPointerException("人为异常"); } if (count == 10) { throw new NullPointerException("测试结束"); } } } public static void main(String[] args) throws InterruptedException { T6_Synchronized t = new T6_Synchronized(); new Thread(t::add, "1").start(); Thread.sleep(1000); new Thread(t::add, "2").start(); } }当第一次异常抛出时,线程2就立即进入执行。
三.synchronized底层原理
synchronized实现锁的基础就是Java对象头,synchronized锁会将线程ID存入mark word(对象头由标记字)。关于mark word,先简要了解一下Java对象。
在Hotspot 虚拟机中,对象在内存中的存储布局,可以分为三个区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。synchronized主要是跟对象头有关系,在对象头中包含了标记字(mark word)、类指针(klass word)和 数组长度(array length)。也就是通过mark word的字节位数来表示各种锁状态。四.synchronized锁升级
● synchronized锁在线程第一次访问的时候,实际上是没有加锁的,只是在mark word中记录了线程ID,默认也就是使用偏向锁。
● 当第二个线程来争用的时候,此时第二个线程会占用cpu,循环等待锁的释放,这时候偏向锁也就升级为自旋锁。