Java - 多线程的同步

1. 线程同步方法

  • 问题的提出

    • 多个线程执行的不确定性引起执行结果的不稳定

    • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据

在这里插入图片描述

  • 关于同步方法的踪迹:

    • 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
    • 非静态的同步方法,同步监视器是:this
    • 静态的同步方法,同步监视器是:当前类本身
    package ThreadSynchronizationTest;
    
    import static java.lang.Thread.sleep;
    
    /**
     * @author : zhilx
     * @Description: Java
     * @version: v1.2
     * @data: 2022/1/21 9:25
     * @node:
     *         v1.0, v1.1 创建三个窗口售票,总票数为10张(通过继承Thread类来实现)
     *         1. 问题:买票过程中,出现了重票和错票问题,出现线程安全问题。
     *         2. 原因:当某个线程操作车票的过程中,在尚未完成操作时,其他线程也参与进来,从而导致出现问问题
     *         3. 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完成ticket操作时
     *                    其他线程才可以进行操作。这种情况,即使线程a出现了阻塞,也不能被改变。
     *         4. 在Java中,我们通过同步机制,来解决线程的安全问题
     *         -4.1 方式一:同步代码块
     *              synchronized(同步监视器) {
     *                  // 需要被同步的代码
     *
     *              }
     *              说明:1. 操作共享数据的代码,即为需要被同步的代码。 --> 不能包代码过多或过少,必须值包含需要被同步的代码
     *                   2. 共享数据:多个线程共同操作的变量。比如:ticket
     *                   3. 同步监视器,俗称:锁。**任何一个类的对象**,都可以充当锁
     *                      要求:多个线程必须要共用同一把锁。
     *                   4. (补充)在通过实现接口来实现多线程的方式中,可以考虑通过使用this来充当同步监视器
     *                   5. (补充)在通过继承来实现多线程的方式中,要慎用this来充当监视器,可以考虑当前类充当同步监视器
     *         -4.2 方式而:同步方法
     *
     *          5. 优点:同步的方式,解决了线程的安全问题
     *             局限性:操作同步代码时,只能有一个线程参与,其他线程等待。相等于是一个单线程的过程,效率较低。
     */
    
    // 方式一: 通过实现Runnable接口来实现多线程,并使用同步代码块方式来解决线程安全问题
    class windows implements Runnable {
        // note: 此时可以不必声明为static的,因为在method2()中多个线程共有同一个windows2的实例化对象
        private int ticket = 10;
    
        // note: 使用同步代码块进行线程安全操作时,需要多个线程共用同一把锁,因此需要共用同一个变量
        Object obj = new Object();
    
        @Override
        public void run() {
            // Object obj = new Object(); // error, 所有的线程必须共用同一把锁
            // 4. (补充)在通过实现接口来实现多线程的方式中,可以考虑通过使用this来充当同步监视器
            // synchronized(obj) {
            synchronized(this) {    // right, 此时this表示windows w = new windows()对象本身
                while (true) {
                    try {
                        sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
                    if (ticket > 0) {
                        // 因为实现的是接口,所有无法直接调用getName()方法,需要调用Thread.currentThread()来获取当前线程
                        System.out.println(Thread.currentThread().getName() + "售出票号为: " + ticket + "的票");
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    
    // 方式一_1 使用代码块的方式解决继承方式实现多线程的 线程同步问题
    class windows2 extends Thread {
        private static int ticket = 10;
    
        // 在继承实现多线程中,需要创建静态变量来解决同步问题
        private static Object obj = new Object();
    
        @Override
        public void run() {
            // 5. (补充)在通过继承来实现多线程的方式中,要慎用this来充当监视器,可以考虑当前类充当同步监视器
            // synchronized(obj) {
            // synchronized(this) { // error, 此时this表示不同的实例化对象w2_1, w2_2, w2_3
            synchronized(windows2.class) { // right, windows2.class为window2的唯一对象
                while (true) {
                    try {
                        sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
                    if (ticket > 0) {
                        System.out.println(getName() + "售出票号为: " + ticket + "的票");
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    
    public class ThreadWindowsDemo {
        public static void main(String[] args) {
            ThreadWindowsDemo demo = new ThreadWindowsDemo();
            demo.method();
            System.out.println("--------------------------");
            // demo.method2();
        }
    
        public void method() {
            windows w = new windows();
            Thread t1 = new Thread(w, "window_1");
            Thread t2 = new Thread(w, "window_2");
            Thread t3 = new Thread(w, "window_3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    
        public void method2() {
            windows2 w2_1 = new windows2();
            windows2 w2_2 = new windows2();
            windows2 w2_3 = new windows2();
    
            w2_1.setName("window2_1");
            w2_2.setName("window2_2");
            w2_3.setName("window2_3");
    
            w2_1.start();
            w2_2.start();
            w2_3.start();
        }
    }
    
    
    package ThreadSynchronizationTest;
    
    import static java.lang.Thread.sleep;
    
    /**
     * @ClassName: ThreadWindowsDemo2
     * @Description: Java的同步方法:使用同步代方法
     * @author: zhilx
     * @version: v1.0
     * @data: 2022/1/21 11:16
     * @node:
     *        4.2 方式二:同步方法
     *        如果操作共享数据的代码完成的声明在一个方法中,我们不妨将此方法声明为同步的。
     */
    
    // 方式二:使用同步方法将多线程声明为同步的
    class windows3 implements Runnable {
        private int ticket = 100;
    
        @Override
        public void run() {
            while (ticket > 0) {
                show();
            }
        }
    
        // note: 此时使用的是同步方法,若没有声明synchronized(obj),则默认为使用了this对象
            private synchronized void show() {
            try {
                sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            if (ticket > 0) {
                // 因为实现的是接口,所有无法直接调用getName()方法,需要调用Thread.currentThread()来获取当前线程
                System.out.println(Thread.currentThread().getName() + "售出票号为: " + ticket + "的票");
                ticket--;
            }
        }
    }
    
    // 使用同步方法解决继承的多线程安全问题
    class windows4 extends Thread {
        private static int ticket = 100;
    
        @Override
        public void run() {
            while (ticket > 0) {
                show();
            }
        }
    
        // note: 此时使用的是同步方法,若没有声明synchronized(obj),则默认为使用了this对象
            // private synchronized void show() { // 此时的同步监视器为w4_1, w4_2, w4_3,所以这个方式是错误的,需要将其声明为static的
            private static synchronized void show() { // 此时的同步监视器为window4.class
            try {
                sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            if (ticket > 0) {
                // 因为实现的是接口,所有无法直接调用getName()方法,需要调用Thread.currentThread()来获取当前线程
                System.out.println(Thread.currentThread().getName() + "售出票号为: " + ticket + "的票");
                ticket--;
            }
        }
    }
    
    public class ThreadWindowsDemo2 {
        public static void main(String[] args) {
            ThreadWindowsDemo2 demo = new ThreadWindowsDemo2();
            // demo.method3();
            System.out.println("--------------------------");
            demo.method4();
        }
    
        public void method3() {
            windows3 w = new windows3();
            Thread t1 = new Thread(w, "window3_1");
            Thread t2 = new Thread(w, "window3_2");
            Thread t3 = new Thread(w, "window3_3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    
        public void method4() {
            windows4 w4_1 = new windows4();
            windows4 w4_2 = new windows4();
            windows4 w4_3 = new windows4();
    
            w4_1.setName("window4_1");
            w4_2.setName("window4_2");
            w4_3.setName("window4_3");
    
            w4_1.start();
            w4_2.start();
            w4_3.start();
        }
    }
    
    

2. 线程安全的单例模式之懒汉式

  • 通过同步方法/同步代码块 将单例模式设置为线程安全的
  • 可以通过外加判断来提高 线程安全懒汉式单例模式的效率
package SingletonPatternTest;

/**
 * @ClassName: SingletonBankTest
 * @Description: Java - 通过同步方式解决懒汉式单例模式中的线程安全问题
 * @author: zhilx
 * @version: v1.0
 * @data: 2022/1/21 20:45
 * @node:
 */
public class SingletonBankTest {
    public static void main(String[] args) {

    }
}

class Bank {
    // 1. 单例设计模式中需要将构造器初始化
    private Bank() {}

    // 2. 私有化Back的一个属性
    private static Bank instance = null;

    // 3. 返回Back属性的instance方法
    /*
    public static Bank getInstance() {
        if (instance == null) {
            instance = new Bank();
        }

        return instance;
    }*/

    // 4. 改为线程安全的getInstance()方法
    // 4.1 直接通过同步方法进行同步
    /*
    public static synchronized Bank getInstance() {
        if (instance == null) {
            instance = new Bank();
        }

        return instance;
    }*/

    // 4.2 通过同步代码块进行同步
    /*
    public static Bank getInstance() {
        synchronized(Bank.class) {
            if (instance == null) {
                instance = new Bank();
            }

            return instance;
        }
    }*/

    // note: 在4.1和4.2的方法中,此时由于所有线程都需要进行同步等待,因此效率较低
    // 4.3 可以对4.2进行改进,来提高单例模式的效率
    public static Bank getInstance() {
        // note: 此时通过首先对instance进行判断,从而避免所有节点均需要进行同步等待
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }

                return instance;
            }
        }
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值