天天看点

Java多线程之synchronized进阶篇

synchronized入门可以参考此篇文章

一、synchronized使用在非静态方法上

  • 如果方法所处的类不是单例synchronized将不能达到自己想要的效果,why?因为synchronized是对象锁如果不是单例的话,就可能new出多个对象,这样不同的线程用不同的对象操作方法那么每个线程都可以拿到锁对象,如下代码可以验证
public class Test {
    public static void main(String[] args) {

        MySynchronized aSynchronized1 = new MySynchronized();
        MySynchronized aSynchronized2 = new MySynchronized();
        MySynchronized aSynchronized3 = new MySynchronized();
        MySynchronized aSynchronized4 = new MySynchronized();

        new Thread(()->{
            aSynchronized1.method(200);
        }).start();
        new Thread(()->{

            aSynchronized2.method(100);
        }).start();
        new Thread(()->{
            /*1、全部用同一个aSynchronized对象的method()方法时控制台只会打印两个线程名,
             *需要等待1000秒之后才能打印出后面的线程名
             * 2、当使用不同的aSynchronized对象的method()方法时控制台会先打印三个线程名,
             * 然后等待1000s继续打印第四个线程名
             * */
            aSynchronized3.method(10000000);
        }).start();
        new Thread(()->{

            aSynchronized4.method(11);
        }).start();
    }
}

class MySynchronized {
    public synchronized void method(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}
           

二、synchronized使用在静态方法上

  • 如果调用同步方法时用的类名.方法名(ClassName.method())将不会发生线程安全问题,因为静态方法使用ClassName.method()的方式调用此时只与类有关而与对象无关,咱们可以这么分析,因为类的字节码文件在项目启动到停止只会加载一次到jvm内存中,同时生成一个Class对象并且是单例对象,因此被synchronized修饰的方法只有一个对象锁,所以是线程安全的。如下代码可以验证此结论
public class Test {
    public static void main(String[] args) {

        new Thread(()->{
            MySynchronized.method(200);
        }).start();
        new Thread(()->{

            MySynchronized.method(100);
        }).start();
        new Thread(()->{

           MySynchronized.method(10000000);
        }).start();

        new Thread(()->{
            MySynchronized.method(11);
        }).start();
    }
}

class MySynchronized {
    public synchronized static void method(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}
           

注意: 如果不同线程用MySynchronized类new出不同的对象调用method()方结论与以上第一点是一致的。

三、synchronized锁升级

  • synchronized在jdk早期版本采用的是重量级锁(重量级锁是在内核态进行的不占用CPU资源)性能相对较底下
  • jdk 1.8对synchronized进行是优化,采用锁升级
    • 偏向锁当第一个线程进来时,锁对象会记录当前线程id,下一个线程要进来时只需比较一下线程id相等则放行,不相等则等待(此过程在用户态,内存中进行),由于锁偏向于第一个进来的线程,因此被称为偏向锁
    • 自旋锁当请求锁的线程变多,synchronized就会升级为自旋锁(此过程也是在用户态,内存中进行的)因为线程变为Blocked状态需要从用户态切换到内核态这个过程代价是比较大的,因此干脆让线程在用户态,内存中不停的去尝试获取锁,这个不停尝试获取锁的过程称之为自旋
    • 重量级锁当有大量线程请求锁时这时还采用自旋锁就有点不合适了,因为自旋是在内存中进行的,因此synchronized需要升级为重量级锁,所谓重量级锁就是使线程进入Blocked状态进行等待,这个过程不消耗内存资源,但是需要切换到内核态,但是对于线程占用锁时间长,线程多的情况下,这点从用户态切换到内核态的代价是可以忽略的
    • 偏向锁、自旋锁、重量级锁优缺点
锁类型 优点 缺点 适用场景
偏向锁 加锁和解锁无需额外的消耗,和无锁方法性能差别极小,在纳秒级别 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 基本没有线程竞争锁的场景
自旋锁 线程不会被阻塞,提高了程序的响应速度 如果一直获取不到锁,将会一直占用内存资源 适用于少量线程竞争锁,并且线程占用锁时间不长的场景
重量级锁 不会出现自旋现象,因此不会占用内存资源 线程可能会长时间阻塞 有很多线程竞争锁,并且线程占用锁时间长