同步关键字Synchronized剖析

一、使用

Synchronized分为:对象锁、类锁

1.对象锁

(1)同步代码块锁

    

@Slf4j
public class Demo1 {
    public static void main(String[] args) throws Exception{
        Person person = new Person();
        new Thread(() -> {
            synchronized (person){
                try {
                    log.debug(Thread.currentThread().getName() +"线程执行了run方法");
                    Thread.sleep(2000L);
                    log.debug(Thread.currentThread().getName() +"线程执行完毕");
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (person){
                try {
                    log.debug(Thread.currentThread().getName() +"线程执行了run方法");
                    Thread.sleep(2000L);
                    log.debug(Thread.currentThread().getName() +"线程执行完毕");
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

总结:使用Synchronized锁住了run方法中的代码块,表示同一个时刻只能有一个线程能够进入代码块。两个线程锁定同一个对象,相当于两个线程只有一把锁,所以要依次执行。

 

同步代码块锁的是对象,假如让每个线程拥有不同的锁,去访问不同的方法资源呢?

@Slf4j
public class Demo1 {
    public static void main(String[] args) throws Exception{
        Person person1 = new Person();
        Person person2 = new Person();
        Thread thread1 = new Thread(() -> {
            synchronized (person1) {
                try {
                    log.debug(Thread.currentThread().getName() + "线程执行了run方法");
                    Thread.sleep(2000L);
                    log.debug(Thread.currentThread().getName() + "线程执行完毕");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (person2) {
                try {
                    log.debug(Thread.currentThread().getName() + "线程执行了run方法");
                    Thread.sleep(2000L);
                    log.debug(Thread.currentThread().getName() + "线程执行完毕");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
        thread2.start();
        //thread1、thread2只要有一个还活着,则一直执行
        while (thread1.isAlive() || thread2.isAlive()){}
        log.debug("主线程执行结束");
    }
}

 

总结:同步代码块锁只是对代码块进行加锁,同一时刻只能有一个线程获取锁,每一把锁只负责当前代码块。两个线程锁定不同的对象,相当于两把锁,各自执行各自的代码互不干扰。

 

(2)方法锁

@Slf4j
public class Demo1 {
    public static void main(String[] args) throws Exception{
        Demo1 demo1 = new Demo1();

        Thread thread1 = new Thread(() -> {
            demo1.getName();
        });

        Thread thread2 = new Thread(() -> {
            demo1.getName();
        });
        thread1.start();
        thread2.start();
        //thread1、thread2只要有一个还活着,则一直执行
        while (thread1.isAlive() || thread2.isAlive()){}
        log.debug("主线程执行结束");
    }

    public synchronized void getName(){
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了getName方法");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行方法getName完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结:两个线程对同一个普通同步方法访问,同一个时刻只能有一个线程进入该方法。

 

@Slf4j
public class Demo1 {
    public static void main(String[] args) throws Exception{
        Demo1 demo1 = new Demo1();

        Thread thread1 = new Thread(() -> {
            demo1.getName();
            demo1.getAge();
        });

        Thread thread2 = new Thread(() -> {
            demo1.getName();
            demo1.getAge();
        });
        thread1.start();
        thread2.start();
        //thread1、thread2只要有一个还活着,则一直执行
        while (thread1.isAlive() || thread2.isAlive()){}
        log.debug("主线程执行结束");
    }

    public synchronized void getName(){
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了getName方法");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行方法getName完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void getAge(){
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了getAge方法");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行方法getAge完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

总结:Synchronized锁定的是对象,所以同一时刻两个方法中只能有一个线程在执行。

 

2.类锁

(1)static方法锁

@Slf4j
public class Demo1 implements Runnable{
    public static void main(String[] args) throws Exception{

        Demo1 demo1 = new Demo1();

        Demo1 demo2 = new Demo1();

        Thread thread1 = new Thread(demo1);

        Thread thread2 = new Thread(demo2);
        thread1.start();
        thread2.start();
        //thread1、thread2只要有一个还活着,则一直执行
        while (thread1.isAlive() || thread2.isAlive()){}
        log.debug("主线程执行结束");
    }

    public static synchronized void getName(){
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了静态方法getName");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行静态方法getName完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override public void run() {
        getName();
    }
}

总结:定义了两个对象,但是类只有一个,所以同一个时刻只能有一个线程访问该方法。如果把static去掉,就是普通的同步方法,两个线程访问两个对象的方法,也就是每个线程拿到不同的锁,就能随便进入方法了。

(2)Class锁

@Slf4j
public class Demo1 implements Runnable{
    public static void main(String[] args) throws Exception{

        Demo1 demo1 = new Demo1();

        Demo1 demo2 = new Demo1();

        Thread thread1 = new Thread(demo1);

        Thread thread2 = new Thread(demo2);
        thread1.start();
        thread2.start();
        //thread1、thread2只要有一个还活着,则一直执行
        while (thread1.isAlive() || thread2.isAlive()){}
        log.debug("主线程执行结束");
    }

    public void getName(){
        synchronized (Demo1.class) {
            try {
                log.debug(Thread.currentThread().getName() + "线程执行了方法getName");
                Thread.sleep(2000L);
                log.debug(Thread.currentThread().getName() + "线程执行方法getName完毕");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
    @Override
    public void run() {
        getName();
    }
}

这个例子Synchronized锁定的是类Demo1.class,而不是对象。发现线程1、线程2会依次执行。

 

总结:如果多个线程执行的是同一把锁,则依次执行,否则各种执行各自的,互不干扰。

 

三、6种常见情况

1.两个线程同时访问一个对象的同步方法

总结:同一时刻只能有一个方法访问成功。

2.两个线程访问两个对象的同步方法

总结:Synchronized包装普通同步方法,是对象锁。由于两个线程访问两个对象的同一个同步方法,也就是这个方法有两把锁,此时锁不起作用,线程1、线程2可以互不干扰的访问该方法。

 

 

3.两个线程访问synchronized的静态方法

 

@Slf4j 
public class Demo1  {

    public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(() -> {
            getName();
        });

        Thread thread2 = new Thread(() -> {
            getName();
        });

        thread1.start();
        thread2.start();
        //thread1、thread2只要有一个还活着,则一直执行
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        log.debug("主线程执行结束");
    }

    public synchronized static void getName() {
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了静态同步方法getName");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行静态同步方法getName完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

总结:两个线程访问同一个静态同步方法,发现此时是同一时刻只能有一个线程能够访问成功。

 

4.两个线程同时访问同步方法和非同步方法

@Slf4j
public class Demo1 implements Runnable{

    public static void main(String[] args) throws Exception {

        Demo1 demo1 = new Demo1();

        Thread thread1 = new Thread(demo1);

        Thread thread2 = new Thread(demo1);

        thread1.start();
        thread2.start();
        //thread1、thread2只要有一个还活着,则一直执行
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        log.debug("主线程执行结束");
    }

    public synchronized  void getName() {
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getName");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getName完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public  void getAge() {
        log.debug(Thread.currentThread().getName() + "线程执行了普通方法getAge");
        log.debug(Thread.currentThread().getName() + "线程执行普通方法getAge完毕");
    }

    @Override
    public void run() {
        getName();
        getAge();
    }
}

总结:两个线程分别同时访问普通同步方法、普通方法。同一时刻普通同步方法只能有一个线程能够访问成功,普通方法不受影响。

 

5.一个线程访问一个类的两个普通同步方法

@Slf4j
public class Demo1 implements Runnable{

    public static void main(String[] args) throws Exception {

        Demo1 demo1 = new Demo1();

        Thread thread1 = new Thread(demo1);

        

        thread1.start();
        //thread1、只要还活着,则一直执行
        while (thread1.isAlive() ) {
        }
        log.debug("主线程执行结束");
    }

    public synchronized  void getName() {
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getName");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getName完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void getAge() {
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getAge");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getAge完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        getName();
        getAge();
    }
}

总结:由于同步代码块锁的是当前对象,this。 而且也只new了一个对象。所以同一时刻只能有一个线程能够访问成功。其实当只有一个线程访问时,上不上锁都是一样的,这里可以认为锁是失效的。

6.同时访问静态同步方法和非静态同步方法

@Slf4j
public class Demo1 implements Runnable{

    public static void main(String[] args) throws Exception {

        Demo1 demo1 = new Demo1();

        Thread thread1 = new Thread(demo1);

        Thread thread2 = new Thread(demo1);

        thread1.start();
        thread2.start();
        //只要有一个线程还活着,则一直执行
        while (thread1.isAlive() ) {
        }
        log.debug("主线程执行结束");
    }

    public synchronized  void getName() {
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getName");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getName完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized static  void getAge() {
        try {
            log.debug(Thread.currentThread().getName() + "线程执行了静态同步方法getAge");
            Thread.sleep(2000L);
            log.debug(Thread.currentThread().getName() + "线程执行静态同步方法getAge完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        getName();
        getAge();
    }
}

 

总结:由此可见,普通同步方法与静态同步方法没有任何关联。普通同步方法锁的是对象,静态同步方法锁的是类。

二、缺点:

1.可重入性

不同方法是可重入的

同一个方法是可重入的

不同类方法是可重入的

2.不可中断

一个线程拿到锁之后,其他线程只能等待当前线程执行完毕获取抛异常,释放锁,其他线程才能获取锁,中间不可被打断。如果当前线程一直占用该锁,其他线程就会处于一直等待状态中。

3.不够灵活

加锁、解锁,每个锁只能有一个对象处理,不适用于分布式场景。

4.无法知道是否成功获取锁

5、效率低

Synchronized是不可中断的,所以当一个等待的线程获取不到锁,就会一直等待,而不去做其他的事情。但是可以设置等待时间,超过等待时间又没有拿到锁,就不会继续等待而去做其他事情

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hi,all

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值