Java synchronized关键字实现线程同步

⭐写在前面⭐

🧭Java 多线程
🎉 内容回顾
Java 多线程介绍及线程创建
Java 多线程七大状态
Java 多线程方法详解
📢今天我们进行 Java synchronized关键字实现线程同步 的学习,感谢你的阅读,内容若有不当之处,希望大家多多指正,一起进步💯!!!
♨️如果觉得博主文章还不错,可以👍三连支持⭐一下哦😀

☘️Java synchronized关键字实现线程同步

☘️多线程卖票问题

问题描述:模拟三个窗口卖火车票的问题,假定一共有100张票。
在这里插入图片描述

🍀继承Thread类的方式

因为继承Thread类的方式创建三个线程需要我们创建三个对象,所以我们需要用static关键字来修饰票的数量来保证三个线程同时卖100张票。

代码示例:

public class SellTickets {
    public static void main(String[] args) {
        Window window1 = new Window();
        Window window2 = new Window();
        Window window3 = new Window();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class Window extends Thread{
    private static int ticket = 100;

    @Override
    public  void run() {
        while (true) {
            if (ticket > 0) {

                try {
                    Thread.sleep(150);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + "卖出一张票,票剩余:" + (--ticket));
            }else {
                System.out.println("票已售罄~~~");
                break;
            }

        }
    }
}

执行结果:
在这里插入图片描述

🍀实现Runuable接口的方式

因为实现Runnable接口的方式创建三个线程我们只需要创建一个对象,然后交给代理类,此时,三个线程用的是同一个对象,也就是保证了三个线程同时卖100张票了,因此也就不需要用static关键字来修饰票的数量了。

代码示例:

public class SellTickets {
    public static void main(String[] args) {
        Window window = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {

                try {
                    Thread.sleep(150);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + "卖出一张票,票剩余:" + (--ticket));
            } else {
                System.out.println("票已售罄~~~");
                break;
            }
        }
    }
}

执行结果:
在这里插入图片描述

🍀超卖现象解读

通过执行结果我们发现不管是用实现Thread类的方式还是用实现Runnable接口的方式创建线程,都出现了超卖的现象,显然在现实生活中这样的现象是不允许的,那么为什么会出现超卖的现象呢?

在这里插入图片描述
假如此时剩余票数为1,两个线程通过检查(1 > 0有票)同时进入卖票的这块代码,一个线程对票数进行 -1 操作后票数变为 0 ,而此时另一个线程也需要对票数 -1 操作后变为 -1 ,也就出现了票数 超卖 的现象,也有可能出现两个线程同时卖了一张票的情况,这就是出现了线程的安全问题

☘️线程安全问题

多个线程同时共享一份数据的时候,就极易容易出现线程安全问题,如上述卖票过程中,出现了重票,错票。

  • 🌻问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
  • 🌻问题的解决:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以操作ticket。在这种情况下即使线程a出现了阻塞,也不能改变。 也就是通过加锁的方式来保证同一时刻内只能有同一个线程来操作。

☘️举个栗子来形象的描述线程的安全问题。

比如我们在商场试衣间试衣服,进试衣间前是不是得先看一下试衣间有没有上锁呢?如果上锁了就表明有人正在使用试衣间,得等人使用完试衣间后才能进去,进去后的第一件事也是上锁,如果不上锁就不能保证其他人不进入试衣间,上完锁后,其他人就必须等待使用完后才能进去,上锁的过程其实就类比于保证线程安全的过程
在这里插入图片描述

☘️同步机制解决线程安全问题

同步机制解决线程安全问题需要我们用到synchronized关键字

🍀同步代码块

同步代码块语法如下:

在这里插入图片描述
🍁 说明

  1. 被同步的代码即为操作共享数据的代码
  2. 共享数据: 多个线程共同操作的变量。如上述示例中的ticket。
  3. 同步监视器: 俗称: 。任何一个类的对象都可以充当锁。( 要求多个线程必须要共用一把锁 )。

🍃同步代码块处理实现Runnable接口的线程安全问题

使用同步代码块的方式解决多线程卖票的安全问题(实现Thread类的形式)。

public class SellTickets {
    public static void main(String[] args) {
        Window window = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window implements Runnable {
    private int ticket = 100;
    private Object obj = new Object();

    @Override
    public void run() {

        while (true) {
            synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "卖出一张票,票剩余:" + (--ticket));
                } else {
                    System.out.println("票已售罄~~~");
                    break;
                }
            }
        }
    }
}

执行结果:
在这里插入图片描述

🌴 代码解读:
在主方法中只new了一个对象(Window window = new Window();)即三个线程同时使用同一把锁,即同一个对象obj,保证了3个线程同一时刻只能有一个线程操作被同步的代码,其他线程必须阻塞,等待释放锁才能进去。
在这里插入图片描述

🍁 补充
因为实现Runnable接口的多个线程,只需要new一次对象,我们可以new一个新的对象作为同步监视器,也可以用当前对象作为同步监视器即传入this
在这里插入图片描述

🍃同步代码块处理继承Thread类的线程安全问题

使用同步代码块的方式解决多线程卖票的安全问题(继承Thread类的形式)。

public class SellTickets {
    public static void main(String[] args) {
        Window1 window1 = new Window1();
        Window1 window2 = new Window1();
        Window1 window3 = new Window1();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class Window1 extends Thread {
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {

        while (true) {
            synchronized (obj) {
                if (ticket > 0) {

                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "卖出一张票,票剩余:" + (--ticket));
                } else {
                    System.out.println("票已售罄~~~");
                    break;
                }
            }
        }
    }
}

执行结果:
在这里插入图片描述

🌴 代码解读:
在主方法中创建3个线程new了三个对象,此时三个线程要想使用同一把锁,即要把对象obj声明为static,保证了3个线程同一时刻只能有一个线程操作被同步的代码,其他线程必须阻塞,等待释放锁才能进去。
在这里插入图片描述

🍁 补充
因为继承Thread类的多个线程,需要new多个对象,我们可以new一个新的static对象作为同步监视器,也可以用类对象作为同步监视器,即传入当前类名.class
在这里插入图片描述

同步代码块解决线程安全问题的注意问题: 同步代码块不能包含多了,也不能包含少了,包含少了可能会解决不了线程安全问题,包含多了会影响执行效率,甚至还可能引发新的问题,比如上述的卖票问题中,如果把whlie语句也归结于同步代码块,就会导致只能有一个线程在卖票,其他线程完全没有机会。

🍀同步方法

同步方法:如果操作的数据代码完整的声明在一个方法中,我们不妨将此方法声明为同步的,仍然用synchronized关键字来修饰,语法如下:
在这里插入图片描述

🍃同步方法处理实现Runnable接口的线程安全问题

将卖票逻辑封装成一个sell方法(实现Runnable接口的形式)。

public class SellTickets03 {
    public static void main(String[] args) {
        Window3 window = new Window3();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window3 implements Runnable {
    private int ticket = 100;
    private Object obj = new Object();

    private synchronized void sell() {
        if (ticket > 0) {
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "卖出一张票,票剩余:" + (--ticket));
        }
    }

    @Override
    public void run() {
        while (true) {
            if (ticket <= 0) {
                System.out.println("票已售罄~~~");
                return;
            }
            sell();
        }
    }
}

执行结果:
在这里插入图片描述

🌴 代码解读:
在主方法中只new了一个对象(Window3 window = new Window3();),将sell方法用synchronized关键字来修饰,此时的同步监视器为当前对象 this ,保证了3个线程同一时刻只能有一个线程进入sell方法的代码,其他线程必须阻塞,等待释放锁才能进去。

🍃同步方法处理继承Thread类的线程安全问题

将卖票逻辑封装成一个sell方法(继承了Thread类的形式)。

public class SellTickets04 {
    public static void main(String[] args) {
        Window4 window1 = new Window4();
        Window4 window2 = new Window4();
        Window4 window3 = new Window4();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class Window4 extends Thread {
    private static int ticket = 100;

    private static synchronized void sell() {
        if (ticket > 0) {
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "卖出一张票,票剩余:" + (--ticket));
        }
    }

    @Override
    public void run() {

        while (true) {
            if (ticket <= 0) {
                System.out.println("票已售罄~~~");
                return;
            }
            sell();
        }
    }
}

执行结果:
在这里插入图片描述

🌴 代码解读:
在主方法中创建3个线程new了三个对象,将sell方法用staticsynchronized关键字来修饰,此时的同步监视器为当前类对象,即 Window4.class 保证了3个线程同一时刻只能有一个线程进入sell方法,其他线程必须阻塞,等待释放锁才能进去。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

WYSCODER

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值