聊聊synchronized

一、概念

多个线程访问同一个资源时,需要对该资源上锁。即同时只允许一个线程访问该资源。任何线程要执行synchronized里的代码,都必须先拿到锁。synchronized底层实现,JVM并没有规定必须应该如何实现,Hotspot在对象头上(64位)拿出2位来记录该对象是不是被锁定,markword,即锁定的是某个对象。

每一个class文件加载到内存后,都会生成Class类的一个对象和加载到内存的代码对应,所以锁静态方法时,锁的是Class类的某个对象(比如T.class)。synchronized锁的都是对象。

1、同步方法和非同步方法可以同时执行

/**
 * @author Java和算法学习:周一
 */
public class T {

    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " m1 start...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m1 end...");
    }

    public void m2() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m2 end...");
    }

    public static void main(String[] args) {
        T t = new T();

        new Thread(()->t.m1(), "t1").start();
        new Thread(()->t.m2(), "t2").start();

//        new Thread(t::m1, "t1").start();
//        new Thread(t::m2, "t2").start();
    }

}

聊聊synchronized_第1张图片

/**
 * @author Java和算法学习:周一
 */
public class Account {

    String name;
    double money;

    public synchronized void set(String name, double money) {
        this.name = name;

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.money = money;
    }

    public /*synchronized*/ double getMoney(String name) {
        return money;
    }

    public static void main(String[] args) {
        Account account = new Account();
        new Thread(() -> account.set("zhang", 100.0)).start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getMoney("zhang"));

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(account.getMoney("zhang"));

    }

}

聊聊synchronized_第2张图片

聊聊synchronized_第3张图片

2、synchronized是可重入锁

一个同步方法可以调用另外一个同步方法,一个线程拥有某个对象的锁,再次申请的时候任然会得到该对象的锁。

比如有个父类的方法加了synchronized,子类重写了该方法,并且在方法里调用了父类的方法,此时是能够调用得到的,如果不是可重入锁,显然是有问题的。

/**
 * @author Java和算法学习:周一
 */
public class T {

    public synchronized void m() {
        System.out.println("m start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m end...");
    }

}

class Child extends T {

    @Override
    public synchronized void m() {
        System.out.println("child m start...");
        super.m();
        System.out.println("child m end...");
    }

    public static void main(String[] args) {
        new Child().m();
    }

}

聊聊synchronized_第4张图片

3、加锁的方法产生异常会释放锁

/**
 * @author Java和算法学习:周一
 */
public class T {
    int count;

    public synchronized void m() {
        while (true) {
            count++;
            System.out.println(Thread.currentThread().getName() + " count=" + count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 3) {
                //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
                int i = 1/0;
            }
        }
    }

    public static void main(String[] args) {
        T t = new T();
        new Thread(()->t.m(), "t1").start();

        new Thread(()->t.m(), "t2").start();
    }

}

聊聊synchronized_第5张图片

4、synchronized(Object)

不能锁String常量、Integer、Long等

二、底层实现

JDK(1.5之前)早期的时候是重量级锁,都是找OS申请锁。

改进:锁升级

sync(this)

Hotspot的实现

1、第一个线程访问时,先在这个对象头上,markword记录这个线程的线程号,此时并没有加锁,这叫偏向锁(即偏向于你)。同一个线程再次访问时,直接使用就可以,效率很高。

2、第二个线程访问时(即有线程争用时),升级为自旋锁占用CPU在等待着(不会进入CPU的等待队列),但不访问OS,所以是在用户态去解决锁的问题,不经过内核态,加锁和解锁的效率比经过内核态的高。

3、默认旋10次后锁再次升级,升级为重量级锁,即去OS申请资源,来加锁,此线程变为等待状态,进入CPU的等待队列里(不再占用CPU资源)。

4、锁只能升级,不能降级。

由此可见:
执行时间长 用重量级锁,
执行时间短 用自旋锁;
线程数多 用重量级锁,
线程数少 用自旋锁。

加锁代码执行时间长,线程数多 加锁代码执行时间短,线程数少
用重量级锁 用自旋锁

三、锁的其他相关知识

1、锁的细化粗化

(1)锁的细化

例如count++前后都有一些业务逻辑,此时synchronized不用加在方法上,可以直接加在count++上。

(2)锁的粗化

比如一个方法里面有很多细的锁,则可以把锁粗化,即把锁加在方法上。

2、锁定对象的改变

锁定某个对象o,如果o的属性发生改变,不影响锁的使用;但是o变成另外一个对象,则锁定的对象发生改变。不想让o改变,则可以定义为final类型的。

你可能感兴趣的:(聊聊synchronized)