通过上一小节的学习,了解到同步代码块可以有效解决线程的安全问题,当把共享资源的操作放在 synchronized 定义的区时,便为这些操作加了同步锁。在方法面前同样可以使用 synchronized 关键字来修饰,被修饰的方法为同步方法,它能实现与同步代码块相同的功能,具体语法格式如下。

    1. synchronized 返回值类型 方法名([参数 1,...]){}

    被 synchronized 修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行该方法。

    接下来通过一个案例来学习同步方法的使用,如下所示。

    1. public class example12 {
    2. public static void main(String[] args) {
    3. //创建线程的任务类对象并开启线程
    4. TicketWinodw task = new TicketWinodw();
    5. new Thread(task, "窗口1").start();
    6. new Thread(task, "窗口2").start();
    7. new Thread(task, "窗口3").start();
    8. new Thread(task, "窗口4").start();
    9. }
    10. }
    11. class TicketWinodw implements Runnable {
    12. private int tickets = 1000;
    13. @Override
    14. public void run() {
    15. while (true) {
    16. sendTicket();
    17. }
    18. }
    19. //定义售票的方法
    20. public synchronized void sendTicket() {
    21. try {
    22. Thread.sleep(10);
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. if (tickets > 0) {
    27. System.out.println(Thread.currentThread().getName() + " --- 卖出的票" + tickets--);
    28. } else {
    29. System.exit(0);
    30. }
    31. }
    32. }

    运行结果如下所示。
    QQ截图20200710144601.png
    上述代码块中,将售票代码抽取为售票方法 sendTicket(),并用 synchronized 关键字把 sendTicket()方法修饰为同步方法,然后在 run()方法中调用该方法。上图所示的运行结果同样没有出现 0 号和负数号的票,说明同步方法实现了和同步代码块一样的效果。

    Think
    同步代码块的锁是自己定义的任意类型的对象,那么同步方法是否也存在锁?如果有,它的锁是什么呢?答案是肯定的,同步方法也有锁,它的锁就是**当前调用该方法的对象,也就是 this 指向的对象。这样做的好处是,同步方法被所有线程锁共享,方法所在的对象相当于所有线程来说是唯一的,从而保证了锁的唯一性。当一个线程执行该方法时,其他的线程就不能进入该方法中,直到这个线程执行完该线程为止。**从而达到了线程同步的效果。

    有时候需要同步的方法是静态方法,静态方法不需要创建对象就可以直接用 类名.方法名() 的方式调用。如果不创建对象,静态同步方法的锁就不会是 this 指向的对象,那么静态同步方法的锁是什么?Java 中静态方法的锁是该方法所在类的 class 对象,该对象在装载该类时自动创建,该对象可以直接用 类名.class 的方式获取。

    用同步代码块和同步方法解决多线程问题有好处也有弊端。同步解决了多个线程同时访问共享数据时的数据安全问题,只要加上同一个锁,在同一时间内只能有一条线程执行。但是线程在执行同步代码时每次都会判断锁的状态,非常消耗资源,效率较低。