【java基础】多线程安全问题解决方式

学习目标:

多线程安全问题的解决方式​


学习内容:

方式一:同步代码块

方式二:Lock锁 -JDK5.0新特性


学习产出1:同步代码块


SafeTicketsWindow01.java

线程的安全问题Demo:
卖票过程中出现了重票和错票的情况 (以下多窗口售票demo存在多线程安全问题)
 

package com.java.thread.thread_secrity;

public class SafeTicketsWindow01 {
    public static void main(String[] args) {
        WindowThread ticketsThread02 = new WindowThread();
        Thread t1 = new Thread(ticketsThread02);
        Thread t2 = new Thread(ticketsThread02);
        Thread t3 = new Thread(ticketsThread02);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class WindowThread implements Runnable {
    private int ticketsNum = 25;

    public void run() {
        while (true) {
            if (ticketsNum > 0) {
                try {
                    //手动让线程进入阻塞,增大错票概率
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":\t票号:" + ticketsNum);
                /*try {
                    //手动让线程进入阻塞,增大重票的概率
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                ticketsNum--;
            } else {
                break;
            }
        }
    }
}
/* 错票分析:
        当票数为 1 的时候,三个线程中有线程被阻塞没有执行票数 -1 的操作,这是其它线程就会通过 if 语句的判断,这样一来就会造成多卖了一张票,出现错票的情况。
        极端情况为,当票数为 1 时,三个线程同时判断通过,进入阻塞,然后多执行两侧卖票操作。
 */

 SafeTicketsWindow02.java

多线程安全问题的解决方式一:同步代码块
        synchronized ( 同步监视器 ) { 需要被同步的代码 }
        优点:同步的方式,解决了线程安全的问题
        缺点:操作同步代码时,只能有一个线程参与,其他线程等待。相当于时一个单线程的过程,效率低。

package com.java.thread.thread_secrity;


public class SafeTicketsWindow02 {
    public static void main(String[] args) {
        WindowThread02 ticketsThread02 = new WindowThread02();
        Thread t1 = new Thread(ticketsThread02);
        Thread t2 = new Thread(ticketsThread02);
        Thread t3 = new Thread(ticketsThread02);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class WindowThread02 implements Runnable {
    private int ticketsNum = 100;

    //由于,Runnable实现多线程,所有线程共用一个实现类的对象,所以三个线程都共用实现类中的这个Object类的对象。
    Object obj = new Object();
    //如果时继承Thread类实现多线程,那么需要使用到static Object obj = new Object();

    public void run() {

        //Object obj = new Object();
        //如果Object对象在run()方法中创建,那么每个线程运行都会生成自己的Object类的对象,并不是三个线程的共享对象,所以并没有给加上锁。

        while (true) {
            synchronized (obj) {
                if (ticketsNum > 0) {
                    try {
                        //手动让线程进入阻塞,增大安全性发生的概率
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":\t票号:" + ticketsNum + "\t剩余票数:" + --ticketsNum);
                } else {
                    break;
                }
            }
        }
    }
}

 学习产出2:Lock锁 -JDK5.0新特性


多线程安全问题的解决方式二:Lock锁 -JDK5.0新特性

        JDK5.0 之后,可以通过实例化 ReentrantLock 对象,在所需要同步的语句前,调用 ReentrantLock 对象的 lock() 方法,实现同步锁,在同步语句结束时,调用 unlock() 方法结束同步锁

 synchronized和lock的异同:(面试题)

        1. Lcok是显式锁(需要手动开启和关闭锁),synchronized是隐式锁,除了作用域自动释放。

        2. Lock只有代码块锁,synchronized有代码块锁和方法锁。

        3. 使用Lcok锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的拓展性(提供更多的子类)

package com.java.thread.thread_secrity;

import java.util.concurrent.locks.ReentrantLock;
 
public class SafeLock {
    public static void main(String[] args) {
        SafeLockThread safeLockThread = new SafeLockThread();
        Thread t1 = new Thread(safeLockThread);
        Thread t2 = new Thread(safeLockThread);
        Thread t3 = new Thread(safeLockThread);

        t1.start();
        t2.start();
        t3.start();
    }
}

class SafeLockThread implements Runnable{
    private int tickets = 100;
    // 重入锁
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (tickets > 0) {
            try {
                //在这里锁住,有点类似同步监视器
                lock.lock();
                if (tickets > 0) {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + ":\t票号:" + tickets + "\t剩余票数:" + --tickets);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //操作完成共享数据后在这里解锁
                lock.unlock();
            }
        }
    }
}
package com.java.thread.thread_secrity;

public class Window02 {
    public static void main(String[] args) {
        Window02Thread ticketsThread02 = new Window02Thread();
        Thread t1 = new Thread(ticketsThread02);
        Thread t2 = new Thread(ticketsThread02);
        Thread t3 = new Thread(ticketsThread02);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class Window02Thread implements Runnable {
    private int tiketsNum = 100;

    @Override
    public void run() {
        while (tiketsNum > 0) {
            show();
        }
    }

    private synchronized void show() { //同步监视器:this
        if (tiketsNum > 0) {
            try {
                //手动让线程进入阻塞,增大安全性发生的概率
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":\t票号:" + tiketsNum + "\t剩余票数:" + --tiketsNum);
        }
    }
}
package com.java.thread.thread_secrity;

/**
 * 多线程安全问题的解决方式二:同步方法
 */
public class Window03 {
    public static void main(String[] args) {
        Window03Thread t1 = new Window03Thread();
        Window03Thread t2 = new Window03Thread();
        Window03Thread t3 = new Window03Thread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.setPriority(Thread.MIN_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);

        t1.start();
        t2.start();
        t3.start();
    }
}

class Window03Thread extends Thread {
    public static int ticketsNum = 100;

    @Override
    public void run() {
        while (ticketsNum > 0) {
            show();
        }
    }

    public static synchronized void show() {//同步监视器:Winddoe03Thread.class  不加static话同步监视器为t1 t2 t3所以错误
        if (ticketsNum > 0) {
            try {
                //手动让线程进入阻塞,增大安全性发生的概率
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":\t票号:" + ticketsNum + "\t剩余票数:" + --ticketsNum);
        }
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值