Java并发编程--2.synchronized
前言
synchronized关键字是互斥锁,也称为内部锁
每个对象都有自己的monitor(锁标记),它是为了分配给线程,最多一个线程可以拥有对象的锁
使用
synchronized修饰成员方法,锁的是当前实例对象
下面是一个例子:
class Thread2 implements Runnable{ private int count; //修饰成员方法,锁的是调用它的对象,该例中也即是调用它的线程 public synchronized void run() { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
调用:
Thread2 t2 = new Thread2(); new Thread(t2).start(); new Thread(t2).start();
synchronized修饰静态方法,锁的是该类的Class对象
下面是一个例子:
class Thread3 implements Runnable { private static int count; //修饰静态方法, 锁的是这个类的所有对象 public static synchronized void getCounter() { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void run() { getCounter(); } }
调用:
Thread3 t3_0 = new Thread3(); Thread3 t3_1 = new Thread3();
new Thread(t3_0).start(); new Thread(t3_1).start();
synchronized修饰代码块,锁的是()中的配置对象
下面是一个例子:
public class Synchronized { private int count; public void getCount(){ for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { Thread1 t1 = new Thread1(new Synchronized()); new Thread(t1).start(); new Thread(t1).start(); } } class Thread1 implements Runnable{ private Synchronized s; public Thread1(Synchronized s) { this.s = s; } @Override public void run() { //修饰代码块: 锁的是()中配置的对象 synchronized(s) { s.getCount(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
synchronized机制
Jvm需要对两类共享数据进行保护:
1.堆中的实例变量
2.本地方法区类变量
对于对象来说 , 有一个监视器保护实例变量; 对于类来说, 有一个监视器保护类变量
Jvm为每个对象和类关联一个锁,如果某个线程获取了锁, 在它释放锁之前其它的线程时不能获得同样的锁的;
对于一个对象,Jvm会维护一个加锁计数器,线程每获得一次该对象, 计数器+1, 每释放一次, 计数器-1,当计数器=0时,锁就完全释放了
死锁
当线程需要持有多个锁时, 就有可能发生死锁的情况, 比如下面这个情形:
A线程首先获得lock1,在获得lock2; B线程首先获得lock2,在获得lock1,
当A线程获得lock1时,B线程获得lock2, 于是A线程等待lock2, B线程等待lock1,
两个线程会无限的等待,这就发生了死锁现象
下面是一个例子:
public class Deadlock { //监视器对象 private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void instanceMethod1(){ synchronized(lock1){ System.out.println("线程1: 获得lock1,等待lock2"); synchronized(lock2) { System.out.println("线程1:获得lock2"); } } } public void instanceMethod2(){ synchronized(lock2){ System.out.println("线程2: 获得lock2,等待lock1"); synchronized(lock1){ System.out.println("线程2: 获得lock1"); } } } public static void main(String[] args){ final Deadlock dld = new Deadlock(); Runnable r1 = new Runnable(){ @Override public void run(){ while(true){ dld.instanceMethod1(); try{ System.out.println("线程1: 睡眠"); Thread.sleep(1000); } catch (InterruptedException ie){ } } } }; Runnable r2 = new Runnable(){ @Override public void run(){ while(true) { dld.instanceMethod2(); try { System.out.println("线程2: 睡眠"); Thread.sleep(1000); } catch (InterruptedException ie){ } } } }; Thread thdA = new Thread(r1); Thread thdB = new Thread(r2); thdA.start(); thdB.start(); } }
控制台输出:
线程1: 获得lock1,等待lock2
线程2: 获得lock2,等待lock1
避免发生死锁
生产中,死锁现象一旦发生,很可能会造成灾难性的后果,我们在编码中应该避免死锁现象发生
1、尽量不要编写在同一时刻需要持有多个锁的代码; 2、创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁;
锁的优化
synchronize采取独占的方式,它属于悲观锁,它假设了最坏的情况,如果持有锁的线程延迟,其他等待程序就会测试,程序停滞不前
锁自旋
在等待锁时,线程不会立即进入阻塞状态,而是先等一段时间看锁是否被释放
偏向锁
一个线程获得了锁,如果在接下来没有别的线程获得该锁,这个锁会偏向第一个获得它的线程,使一个线程多次获得锁的代价更低
锁膨胀
多次调用粒度太小的锁, 不如一次调用粒度大的锁
轻量级锁
如果存在锁的竞争,除了互斥量的开销外,还会发生CAS操作,在同步期间如果没有锁竞争,使用轻量级锁避免互斥量的开销
祝:
大家生活愉快,工作顺利