IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Java synchronized关键字实现线程同步 -> 正文阅读

[移动开发]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方法,其他线程必须阻塞,等待释放锁才能进去。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:22:54  更:2022-04-04 12:23:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 20:26:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码