synchronized 怎么使用
创始人
2024-05-04 00:53:55
0

文章目录

  • 前言
  • 通过一系列的例子,了解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"); //newRunnableThread thread2 = new Thread(newRunnable2, "线程2");//newRunnable2thread1.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、使用同步是需要系统很大开销的,所以非必要情况下不要进行同步锁操作。

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...