【Java】同步方法

一、总述

刚刚我们已经学习完了同步代码块,就是将一段代码锁起来,这样就可以解决多线程操作共享数据时带来的数据安全问题。

但是如果我们想要将一个方法里面所有的代码全部锁起来,此时就没有必要去用同步代码块了,我们可以直接将 synchronized 加在方法上,此时这个方法就叫做同步方法。

修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

特点1:同步方法是锁住方法里面所有的代码

特点2:同步方法的锁对象,我们是不能自己指定的,是Java已经规定好的。

  • 如果你当前的方法是非静态的,它的锁对象就是 this,即当前方法的调用者。
  • 如果你当前的方法是静态的,它的锁对象就是 当前类的字节码文件对象

二、练习

题目还是刚刚的题目,只不过要求用同步方法完成。

很多同学在写同步方法的时候都会有一个小疑问:不知道把哪些方法写在同步方法中。

技巧:你首先不要写同步方法,而是先写同步代码块,然后再把同步代码块里面的代码去抽取成方法就行了。

需求:
     某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
     利用同步方法完成
     技巧:同步代码块

我们先用同步代码块写,再去改成同步方法。

由于在之前我们用的都是多线程的第一种实现方式,这里就使用第二种实现方式。

由于现在使用的是第二种实现方式,此时 ticket 的前面就没必要去加 static 了。

我们之前一直使用第一种实现方式(继承Thread类),此时它是有可能创建多个对象的,所以如果我想让所有的对象都共享一个 ticket成员变量的值,在前面就需要加static。

image-20240506182109806

但是我们现在使用的是第二种实现方式(实现Runnable接口),在这种方式下,MyRunnable(我们自己写的类)只有可能会创建一次,它是作为一个参数让线程去执行的,所以只会创建一次它的对象。

那么既然只会创建一次,ticket就没有必要再去加static了。

public class MyRunnable implements Runnable {
    int ticket = 0;
    @Override
    public void run() {
    }
}

接下来完善 run(),写 run() 的时候也是有套路的

public class MyRunnable implements Runnable {

    int ticket = 0;

    @Override
    public void run() {
        //1.循环
        while (true) {
            //2.同步代码块(同步方法)
            //3.判断共享数据是否到了末尾,如果到了末尾
            if (ticket == 100) {
                break;
            } else {
                //4.判断共享数据是否到了末尾,如果没有到末尾
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
            }
        }
    }
}

测试类

MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

t1.start();
t2.start();
t3.start();

接下来改成同步方法。

改写很简单:将synchronized里面所有的代码全都放到方法中即可。

选中synchronized里面所有的代码,ctrl + alt + M ,此时就可以抽取成一个方法了。

public class MyRunnable implements Runnable {

    int ticket = 0;

    @Override
    public void run() {
        //1.循环
        while (true) {
            //2.同步代码块(同步方法)
            if (method()) break;
        }
    }

    //this
    private synchronized boolean method() {
        //3.判断共享数据是否到了末尾,如果到了末尾
        if (ticket == 100) {
            return true;
        } else {
            //4.判断共享数据是否到了末尾,如果没有到末尾
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
        }
        return false;
    }
}

三、StringBuffer

学习完同步方法后,我们之前有很多知识点其实也就可以解释了。

很多同学在之前肯定听过:字符串在进行拼接的时候,除了 StringBuilder 外,还有一个类:StringBuffer

那这个 StringBuffer 跟我们之前学习的 StringBuilder 有什么区别呢?

打开 API帮助文档 来看一下。

这里打开了两个 API帮助文档,左边是 StringBuilder,右边是 StringBuffer

image-20240506192619700

往下滑,可以惊讶的发现,方法都是一模一样的。

image-20240506192721093

那为什么Java要定义两个完全一模一样的类呢?

看下面,StringBuilder下面有这样一句话:将 StringBuilder 的实例(即对象的意思)用于多个线程是不安全的。如果需要这样的同步,则建议使用 StringBuffer

image-20240506192904651

接下来看下源码,同样左边是 StringBuilder,右边是 StringBuffer

往下拉,找到成员方法。可以发现,StringBuffer 里面每一个成员方法都有 synchronized,但左边的 StringBuilder 所有的成员方法都没有 synchronized,这就表示 StringBuffer 是线程安全的,因为它里面所有的方法前面都有 synchronized,即它里面所有的方法都是同步的。

image-20240506194200650

那我们以后如何选择呢?

如果你的代码是单线程的,不需要考虑多线程中数据安全的情况,就用 StringBuilder 就行了。

但如果是多线程环境下,需要考虑到 数据安全,这时候就可以选择右边的 StringBuffer

  • 35
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值