java 的 synchronized 同步方法使用场景举例解读

导读:本篇文章讲解 java 的 synchronized 同步方法使用场景举例解读,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

前言:

线程同步是指多个线程同时访问某一个共享资源,保证在同一时间只能有一个线程访问该资源。

java 一般用 synchronized 关键字来做线程同步,简单实用,下面直接给个例子说明一下。

一、 举个买票的例子

一般大家也会用这个来举例,很常见也便于理解。

1. 首先创建一个Ticket 类用于表述车票,如下:

/**
 * 车票类
 */
public class Ticket {
    private Integer ticketNum;

    Ticket(int ticketNum) {
        this.ticketNum = ticketNum;
    }

     // synchronized 这里先注销这个同步关键字
     void buyTicket(int amount) {

        System.out.println("当前可售票数: " + ticketNum);
        if (ticketNum < amount) {
            System.out.println("目前剩余票数" + ticketNum + ", 购票" + amount + ", 可售票数不足");
        } else {
            ticketNum = ticketNum - amount;
            System.out.println("购票" + amount + ", 剩余票数" + ticketNum);
            if (ticketNum <= 0) {
                System.out.println("票售罄");
            }
        }

    }
}

2. 创建一个客户端类 BuyTicketClient 用于提供买票服务

/**
 * 购票服务端
 */

public class BuyTicketClient implements Runnable {

    Ticket ticket;
    int amount;

    BuyTicketClient(Ticket ticket, int amount) {
        this.ticket = ticket;
        this.amount = amount;

    }

    public void run() {
        ticket.buyTicket(amount);
    }
}

3. 模拟多人购票

/**
 * 模拟多人购票
 */

public class Buy {
    public static void main(String[] args) {
        Ticket ticket = new Ticket(5);
        BuyTicketClient buyTicketClient_1 = new BuyTicketClient(ticket, 2);
        BuyTicketClient buyTicketClient_2 = new BuyTicketClient(ticket, 1);
        BuyTicketClient buyTicketClient_3 = new BuyTicketClient(ticket, 3);

        Thread thread_3 = new Thread(buyTicketClient_3);
        Thread thread_2 = new Thread(buyTicketClient_2);
        Thread thread_1 = new Thread(buyTicketClient_1);

        thread_3.start();
        thread_2.start();
        thread_1.start();
    }
}

运行看一下结果(每次运行结果可能都不同):

当前可售票数: 5
当前可售票数: 5
当前可售票数: 2
目前剩余票数0, 购票1, 可售票数不足
购票2, 剩余票数0
购票3, 剩余票数2
票售罄
票售罄

会发现输出结果完全对不上,主要原因是多线程执行顺序不可控,票数量(ticketNum字段)修改是混乱的。

4. 加 synchronized 字段做同步

步骤1代码中buyTicket方法加 synchronized字段,如下

/**
 * 车票类
 */
public class Ticket {
    private Integer ticketNum;

    Ticket(int ticketNum) {
        this.ticketNum = ticketNum;
    }

     synchronized void buyTicket(int amount) {

        System.out.println("当前可售票数: " + ticketNum);
        if (ticketNum < amount) {
            System.out.println("目前剩余票数" + ticketNum + ", 购票" + amount + ", 可售票数不足");
        } else {
            ticketNum = ticketNum - amount;
            System.out.println("购票" + amount + ", 剩余票数" + ticketNum);
            if (ticketNum <= 0) {
                System.out.println("票售罄");
            }
        }

    }
}

再次运行步骤3的代码,结果如下(每次运行结果可能不同):

当前可售票数: 5
购票3, 剩余票数2
当前可售票数: 2
购票1, 剩余票数1
当前可售票数: 1
目前剩余票数1, 购票2, 可售票数不足

显示结果是合理的。

二、synchronized 使用说明

synchronized 一般涉及三种使用场景

1. 修饰实例方法,作用于当前对象实例,进入同步代码前要获得当前实例的锁(上述买票例子就是对实例方法 buyTicket() 加锁)

另外:一个线程正在访问一个对象实例的 synchronized 实例方法,那么其他线程不能访问该对象实例的其他 synchronized 方法,毕竟一个对象实例只有一把锁,当一个线程获取了该对象实例的锁之后,其他线程无法获取该对象实例的锁,所以无法访问该对象实例的其他synchronized实例方法

2. 修饰代码块,作用于对象实例,进入同步代码前要获得当前实例的锁,上述 Ticket  类改一下就是,如下:

public class Ticket {
    private Integer ticketNum;

    Ticket(int ticketNum) {
        this.ticketNum = ticketNum;
    }

    void buyTicket(int amount) {
        synchronized (ticketNum) {
            System.out.println("当前可售票数: " + ticketNum);
            if (ticketNum < amount) {
                System.out.println("目前剩余票数" + ticketNum + ", 购票" + amount + ", 可售票数不足");
            } else {
                ticketNum = ticketNum - amount;
                System.out.println("购票" + amount + ", 剩余票数" + ticketNum);
                if (ticketNum <= 0) {
                    System.out.println("票售罄");
                }
            }
        }

    }
}

上述代码是对 Integer 类型的 ticketNum对象实例加锁,大多时候推荐使用 this 替换 ticketNum,this 表示对当前实例加锁(到这里和实例方法加锁是一样的含义)

同时,还可以把 ticketNum 换成 Ticket.class(类锁),这个在下面细说。

3. 修饰静态方法,作用当前类,进入同步代码库前要获得给定类的锁(就是2中说的类锁)

这个很有意思,举个例子说明一下和实例方法加锁的区别

public class SynTwoMethod implements Runnable {

    static int i = 0;

    public synchronized void increase() {
        i++;
    }


    public static synchronized void increaseStatic() {
        i++;
    }

    public synchronized void increaseClass() {
        synchronized (SynTwoMethod.class) {
            i++;
        }
    }

    public void run() {
        for (int j = 0; j < 100000; j++) {
            increase();
//            increaseStatic();
//            increaseClass();
        }
    }

}


//测试

public class SynTwoMethodTest {
    public static void main(String[] args) throws InterruptedException {
        SynTwoMethod synTwoMethod_1 = new SynTwoMethod();
        SynTwoMethod synTwoMethod_2 = new SynTwoMethod();
        Thread thread_1 = new Thread(synTwoMethod_1);
        Thread thread_2 = new Thread(synTwoMethod_2);
        thread_1.start();
        thread_2.start();
        thread_1.join();
        thread_2.join();
        System.out.println(synTwoMethod_1.i);
    }
}


increase() 方法是实例方法加锁,increaseStatic() 是静态方法加锁,increaseClass() 是通过代码块对对象加锁;测试方法生成了同一对象的不同实例;在run() 方法里分别执行三种方法会发现:

increase 得到的结果是小于 200000,increaseStatic 和 increaseClass 得到的结果都是 200000。

结论:存在类锁的类,无论生成多少个实例,在执行加锁方法时都会单一执行;而实例加锁只对同一个实例有效。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/16527.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!