synchronized 怎么使用

文章目录

  • 前言
  • 通过一系列的例子,了解synchronized 使用
  • 总结

前言

上一篇了解了synchronized,但是呢光懂理论没用,关键是要会用,用demo的形式写一下各种使用场景,这么一来,就会对synchronized的使用更加透彻。

通过一系列的例子,了解synchronized 使用

1、synchronized 都会在哪些地方使用?

修饰一个代码块,作用的对象是调用这个代码块的对象。
修饰一个方法,作用的对象是调用这个方法的对象。
修饰一个静态方法,作用是这个类的所有对象。
修饰一个类,作用的是这个类的所有对象

2、怎么使用同步代码块?

两个线程访问同一个对象代码块,只有一个线程执行,另外一个线程被阻塞。
比如:

class MyRunnable implements Runnable{
  private static int count;//定义一个变量count;
  public MyRunnable(){
    count = 0;
  }
  @Override public void run() {
    synchronized (this){//同步代码块,锁住的是MyRunnable这个实例对象
      for (int i = 0; i < 5; i++){
        try {
          System.out.println(Thread.currentThread().getName() + ":" + (count++));
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }

    }
  }
  public int getCount(){
    return count;
  }
}

输出的日志如下
2022-12-31 14:36:03.266 8015-8053/com.ssz.mvvmdemo I/System.out: 线程1:0
2022-12-31 14:36:03.366 8015-8053/com.ssz.mvvmdemo I/System.out: 线程1:1
2022-12-31 14:36:03.467 8015-8053/com.ssz.mvvmdemo I/System.out: 线程1:2
2022-12-31 14:36:03.567 8015-8053/com.ssz.mvvmdemo I/System.out: 线程1:3
2022-12-31 14:36:03.667 8015-8053/com.ssz.mvvmdemo I/System.out: 线程1:4
2022-12-31 14:36:03.768 8015-8054/com.ssz.mvvmdemo I/System.out: 线程2:5
2022-12-31 14:36:03.868 8015-8054/com.ssz.mvvmdemo I/System.out: 线程2:6
2022-12-31 14:36:03.968 8015-8054/com.ssz.mvvmdemo I/System.out: 线程2:7
2022-12-31 14:36:04.068 8015-8054/com.ssz.mvvmdemo I/System.out: 线程2:8
2022-12-31 14:36:04.169 8015-8054/com.ssz.mvvmdemo I/System.out: 线程2:9

可以看到先执行线程1,再执行线程2,达到了同步目的。

2.1 这个时候,我们将执行的对象换一下

比如:

    MyRunnable myRunnable = new MyRunnable();
    MyRunnable myRunnable2 = new MyRunnable();
    Thread thread1 = new Thread(myRunnable, "线程1");
    Thread thread2 = new Thread(myRunnable2, "线程2");
    thread1.start();
    thread2.start();

执行结果:
2022-12-31 14:42:33.957 8237-8273/com.ssz.mvvmdemo I/System.out: 线程1:0
2022-12-31 14:42:33.957 8237-8274/com.ssz.mvvmdemo I/System.out: 线程2:1
2022-12-31 14:42:34.057 8237-8273/com.ssz.mvvmdemo I/System.out: 线程1:2
2022-12-31 14:42:34.057 8237-8274/com.ssz.mvvmdemo I/System.out: 线程2:2
2022-12-31 14:42:34.157 8237-8273/com.ssz.mvvmdemo I/System.out: 线程1:3
2022-12-31 14:42:34.157 8237-8274/com.ssz.mvvmdemo I/System.out: 线程2:3
2022-12-31 14:42:34.257 8237-8273/com.ssz.mvvmdemo I/System.out: 线程1:4
2022-12-31 14:42:34.257 8237-8274/com.ssz.mvvmdemo I/System.out: 线程2:4
2022-12-31 14:42:34.358 8237-8274/com.ssz.mvvmdemo I/System.out: 线程2:5
2022-12-31 14:42:34.358 8237-8273/com.ssz.mvvmdemo I/System.out: 线程1:5

可以看到线程1和线程2,变成随机的执行代码块,为什么呢?
因为他们作用的不是同一个对象,线程1 执行的是 myRunnable对象,
而线程2 执行的是 myRunnable2对象。所以他们互不干扰,两个线程就能同时执行。

2.2 当一个线程访问一个对象的 synchronized(this) 代码块的时候,另外一个线程还是可以访问, 该对象的没有被synchronized(this) 修饰的代码块。

比如:

class Counter implements Runnable{
  private int count;
  public Counter(){
    count = 0;
  }

  /**
   * 对count 执行自增操作
   * */
  public void countAdd(){
    synchronized (this){
      for (int i = 0; i < 5; i++){
        try {
          System.out.println(Thread.currentThread().getName()+ ":" + (count++));
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
  //没有对count 执行自增操作,只是打印
  public void printCount(){
    for(int i = 0; i < 5; i++){
      try {
        System.out.println(Thread.currentThread().getName() + "  count:" + count);
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
  @Override public void run() {
    String threadName = Thread.currentThread().getName();
    if ("线程A".equals(threadName)){//线程A 要执行自增操作
       countAdd();
    }else if("线程B".equals(threadName)){
       printCount();
    }
  }
}

使用:
    Counter counter = new Counter();
    Thread threadA = new Thread(counter, "线程A");
    Thread threadB = new Thread(counter, "线程B");
    threadA.start();
    threadB.start();

结果:
2022-12-31 15:03:52.249 9366-9403/com.ssz.mvvmdemo I/System.out: 线程A:0
2022-12-31 15:03:52.249 9366-9404/com.ssz.mvvmdemo I/System.out: 线程B  count:1
2022-12-31 15:03:52.349 9366-9404/com.ssz.mvvmdemo I/System.out: 线程B  count:1
2022-12-31 15:03:52.349 9366-9403/com.ssz.mvvmdemo I/System.out: 线程A:1
2022-12-31 15:03:52.449 9366-9403/com.ssz.mvvmdemo I/System.out: 线程A:2
2022-12-31 15:03:52.449 9366-9404/com.ssz.mvvmdemo I/System.out: 线程B  count:2
2022-12-31 15:03:52.549 9366-9403/com.ssz.mvvmdemo I/System.out: 线程A:3
2022-12-31 15:03:52.549 9366-9404/com.ssz.mvvmdemo I/System.out: 线程B  count:3
2022-12-31 15:03:52.650 9366-9403/com.ssz.mvvmdemo I/System.out: 线程A:4
2022-12-31 15:03:52.650 9366-9404/com.ssz.mvvmdemo I/System.out: 线程B  count:5

可以看到线程A 和 线程B 他们是互不影响的,线程B还是随机执行的。
也就是说一个线程 在执行同一个对象的 synchronized(this)的代码块时候,
另外一个线程可以执行该对象的没有被synchronized(this)修饰的代码块,不会阻塞。

3、怎么给一个对象加锁呢?
class Account {
  String name;
  float amount;

  public Account(String name, float amount){
    this.name = name;
    this.amount = amount;
  }

  /**
   * 存钱
   * */
  public void save(float money){
    amount += money;
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  /**
   * 取钱
   * */
  public void out(float money){
    amount -= money;
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }


  public float getAmount(){
    return amount;
  }
}

class AccountOperator implements Runnable{
  private Account account;
  public AccountOperator(Account account){
    this.account = account;
  }
  @Override public void run() {
    synchronized (account){//对这个对象进行加锁操作
      account.save(500);//存钱500;
      account.out(500);//取钱500;
      System.out.println(Thread.currentThread().getName() + ":" + account.amount);
    }
  }
}

使用:
    Account account = new Account("ssz", 10000.0f);
    AccountOperator accountOperator = new AccountOperator(account);
    final int THREAD_NUM = 10;
    //创建5个线程去随机执行。
    Thread[] threads = new Thread[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; i++){
      threads[i] = new Thread(accountOperator, "Thread" + i);
      threads[i].start();
    }



结果:
2022-12-31 15:31:09.675 11884-11927/com.ssz.mvvmdemo I/System.out: Thread0:10000.0
2022-12-31 15:31:09.875 11884-11928/com.ssz.mvvmdemo I/System.out: Thread1:10000.0
2022-12-31 15:31:10.076 11884-11930/com.ssz.mvvmdemo I/System.out: Thread2:10000.0
2022-12-31 15:31:10.276 11884-11931/com.ssz.mvvmdemo I/System.out: Thread3:10000.0
2022-12-31 15:31:10.477 11884-11934/com.ssz.mvvmdemo I/System.out: Thread5:10000.0
2022-12-31 15:31:10.677 11884-11932/com.ssz.mvvmdemo I/System.out: Thread4:10000.0
2022-12-31 15:31:10.878 11884-11936/com.ssz.mvvmdemo I/System.out: Thread7:10000.0
2022-12-31 15:31:11.078 11884-11937/com.ssz.mvvmdemo I/System.out: Thread8:10000.0
2022-12-31 15:31:11.279 11884-11938/com.ssz.mvvmdemo I/System.out: Thread9:10000.0
2022-12-31 15:31:11.480 11884-11935/com.ssz.mvvmdemo I/System.out: Thread6:10000.0

可以看到线程是随机的执行,但是呢,对于存钱取钱的操作仍然是不受影响的,因为这块是进行了同步存取操作,是对Account进行了加锁操作,保证了不受其他线程的干扰。

3.1 假如没有明确的对象作为锁,只想同步一段代码块怎么办呢?
class Test implements Runnable{
  private byte[] lock = new byte[0]//一个特殊的实例对象

  public void test(){
    synchronized(lock){
        //todo 要同步的代码
    }
  }

  public void run(){
  }

}

为什么使用byte[] 作为对象呢?
因为byte 数组创建起来比任何对象都经济。
跟Object object = new Object() 比起来的话,查看编译后的字节码发现。
byte 数组 只需要3行操作码,而 object 需要7行操作码

3.2 synchronized 在修饰方法的时候能不能继承呢?

synchronized 是不能继承的,也就是如果子类要同步,
要嘛调用父类的同步方法,要嘛就是在方法前,添加一个synchronized关键字。
比如:

class Parent {
   public synchronized void method() {   }
}
class Child extends Parent {
   public void method() { super.method();   }//调用父类的同步方法
}

class Parent {
   public synchronized void method() { }
}
class Child extends Parent {
   public synchronized void method() { } //添加关键字
}

4、怎么修饰一个静态方法呢?

当我们在修饰静态方法的时候,因为静态方法是属于类的,所以锁住的是类的所有对象。
比如:

class NewRunnable implements Runnable{
  private static int count;
  public NewRunnable(){
    count = 0;
  }

  public synchronized static void method(){  //修饰的是一个静态方法
    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() {
    method();
  }
}

使用:
    NewRunnable newRunnable = new NewRunnable();
    NewRunnable newRunnable2 = new NewRunnable();
    Thread thread1 = new Thread(newRunnable, "线程1"); //newRunnable
    Thread thread2 = new Thread(newRunnable2, "线程2");//newRunnable2
    thread1.start();
    thread2.start();

结果:
2022-12-31 17:02:21.483 20792-20824/com.ssz.mvvmdemo I/System.out: 线程1:0
2022-12-31 17:02:21.583 20792-20824/com.ssz.mvvmdemo I/System.out: 线程1:1
2022-12-31 17:02:21.683 20792-20824/com.ssz.mvvmdemo I/System.out: 线程1:2
2022-12-31 17:02:21.783 20792-20824/com.ssz.mvvmdemo I/System.out: 线程1:3
2022-12-31 17:02:21.884 20792-20824/com.ssz.mvvmdemo I/System.out: 线程1:4
2022-12-31 17:02:21.984 20792-20825/com.ssz.mvvmdemo I/System.out: 线程2:5
2022-12-31 17:02:22.084 20792-20825/com.ssz.mvvmdemo I/System.out: 线程2:6
2022-12-31 17:02:22.184 20792-20825/com.ssz.mvvmdemo I/System.out: 线程2:7
2022-12-31 17:02:22.285 20792-20825/com.ssz.mvvmdemo I/System.out: 线程2:8
2022-12-31 17:02:22.385 20792-20825/com.ssz.mvvmdemo I/System.out: 线程2:9

可以看到,虽然是不同的对象,但是呢,却还是按顺序执行了。最主要是同步的是静态方法,而静态方法是属于类的,这相当于锁住了这个类,所以,就能阻止其他线程调用,只能等待第一个线程执行完,再执行第二个线程。

4.1 怎么修饰一个类呢?

我们对上面修饰静态方法做个改造,就是把synchronized 放到方法里头,然后使用 synchronized (NewRunnable.class)
比如:

class NewRunnable implements Runnable{
  private static int count;
  public NewRunnable(){
    count = 0;
  }

  public void method(){    //不是静态方法,就是普通方法
    synchronized (NewRunnable.class){ //锁的作用对象是类
      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() {
    method();
  }
}
结果情况1:
2022-12-31 17:10:52.476 22805-22840/com.ssz.mvvmdemo I/System.out: 线程2:0
2022-12-31 17:10:52.577 22805-22840/com.ssz.mvvmdemo I/System.out: 线程2:1
2022-12-31 17:10:52.677 22805-22840/com.ssz.mvvmdemo I/System.out: 线程2:2
2022-12-31 17:10:52.778 22805-22840/com.ssz.mvvmdemo I/System.out: 线程2:3
2022-12-31 17:10:52.878 22805-22840/com.ssz.mvvmdemo I/System.out: 线程2:4
2022-12-31 17:10:52.978 22805-22839/com.ssz.mvvmdemo I/System.out: 线程1:5
2022-12-31 17:10:53.078 22805-22839/com.ssz.mvvmdemo I/System.out: 线程1:6
2022-12-31 17:10:53.179 22805-22839/com.ssz.mvvmdemo I/System.out: 线程1:7
2022-12-31 17:10:53.279 22805-22839/com.ssz.mvvmdemo I/System.out: 线程1:8
2022-12-31 17:10:53.379 22805-22839/com.ssz.mvvmdemo I/System.out: 线程1:9

结果情况2:
2022-12-31 17:12:17.787 23462-23496/com.ssz.mvvmdemo I/System.out: 线程1:0
2022-12-31 17:12:17.887 23462-23496/com.ssz.mvvmdemo I/System.out: 线程1:1
2022-12-31 17:12:17.987 23462-23496/com.ssz.mvvmdemo I/System.out: 线程1:2
2022-12-31 17:12:18.087 23462-23496/com.ssz.mvvmdemo I/System.out: 线程1:3
2022-12-31 17:12:18.187 23462-23496/com.ssz.mvvmdemo I/System.out: 线程1:4
2022-12-31 17:12:18.288 23462-23497/com.ssz.mvvmdemo I/System.out: 线程2:5
2022-12-31 17:12:18.388 23462-23497/com.ssz.mvvmdemo I/System.out: 线程2:6
2022-12-31 17:12:18.489 23462-23497/com.ssz.mvvmdemo I/System.out: 线程2:7
2022-12-31 17:12:18.589 23462-23497/com.ssz.mvvmdemo I/System.out: 线程2:8
2022-12-31 17:12:18.689 23462-23497/com.ssz.mvvmdemo I/System.out: 线程2:9

我们看到可能先执行的线程1,也可能先执行的线程2,但是不管是谁开始执行,只要谁先开始,另外一个线程就得等着。
所以,这就保证了同步。
这里是给这个类加锁,所以他们都是共用1把锁,只有一方把锁释放,另外一方才能执行。

5 使用synchronized 有哪些注意点呢?

1、定义接口的时候,不能使用synchronized 关键字。
2、构造方法不能使用synchronized 关键字,但可以使用synchronized来同步代码块。

总结

总的来讲:
1、synchronized 关键字用在非静态的方法上,或者对象上,所获得的锁是针对对象的;
如果是一个静态方法,或者一个类,那么锁是针对类的所有对象的。
2、每个对象只有一把锁与之关联,需要等一方释放,另一方才能执行。
3、使用同步是需要系统很大开销的,所以非必要情况下不要进行同步锁操作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容