文章目录
上一篇了解了synchronized,但是呢光懂理论没用,关键是要会用,用demo的形式写一下各种使用场景,这么一来,就会对synchronized的使用更加透彻。
修饰一个代码块,作用的对象是调用这个代码块的对象。
修饰一个方法,作用的对象是调用这个方法的对象。
修饰一个静态方法,作用是这个类的所有对象。
修饰一个类,作用的是这个类的所有对象
两个线程访问同一个对象代码块,只有一个线程执行,另外一个线程被阻塞。
比如:
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,达到了同步目的。
比如:
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对象。所以他们互不干扰,两个线程就能同时执行。
比如:
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)修饰的代码块,不会阻塞。
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进行了加锁操作,保证了不受其他线程的干扰。
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行操作码
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() { } //添加关键字
}
当我们在修饰静态方法的时候,因为静态方法是属于类的,所以锁住的是类的所有对象。
比如:
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
可以看到,虽然是不同的对象,但是呢,却还是按顺序执行了。最主要是同步的是静态方法,而静态方法是属于类的,这相当于锁住了这个类,所以,就能阻止其他线程调用,只能等待第一个线程执行完,再执行第二个线程。
我们对上面修饰静态方法做个改造,就是把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把锁,只有一方把锁释放,另外一方才能执行。
1、定义接口的时候,不能使用synchronized 关键字。
2、构造方法不能使用synchronized 关键字,但可以使用synchronized来同步代码块。
总的来讲:
1、synchronized 关键字用在非静态的方法上,或者对象上,所获得的锁是针对对象的;
如果是一个静态方法,或者一个类,那么锁是针对类的所有对象的。
2、每个对象只有一把锁与之关联,需要等一方释放,另一方才能执行。
3、使用同步是需要系统很大开销的,所以非必要情况下不要进行同步锁操作。