Java并发编程--2.synchronized

前言

synchronized关键字是互斥锁,也称为内部锁ide

每一个对象都有本身的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();
              }
           }
    }
}

调用:this

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();
    }
}

调用:spa

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须要对两类共享数据进行保护:code

1.堆中的实例变量

2.本地方法区类变量

对于对象来讲 , 有一个监视器保护实例变量; 对于类来讲, 有一个监视器保护类变量对象

 

Jvm为每一个对象和类关联一个锁,若是某个线程获取了锁, 在它释放锁以前其它的线程时不能得到一样的锁的;blog

对于一个对象,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

避免发生死锁

生产中,死锁现象一旦发生,极可能会形成灾难性的后果,咱们在编码中应该避免死锁现象发生

、尽可能不要编写在同一时刻须要持有多个锁的代码;

、建立和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用做单个对象的对象级别锁;

锁的优化

synchronize采起独占的方式,它属于悲观锁,它假设了最坏的状况,若是持有锁的线程延迟,其余等待程序就会测试,程序停滞不前

锁自旋

在等待锁时,线程不会当即进入阻塞状态,而是先等一段时间看锁是否被释放

偏向锁

一个线程得到了锁,若是在接下来没有别的线程得到该锁,这个锁会偏向第一个得到它的线程,使一个线程屡次得到锁的代价更低 

锁膨胀

屡次调用粒度过小的锁, 不如一次调用粒度大的锁

轻量级锁

若是存在锁的竞争,除了互斥量的开销外,还会发生CAS操做,在同步期间若是没有锁竞争,使用轻量级锁避免互斥量的开销