Java中解决线程安全问题的三种方法:同步代码块、同步方法、Lock锁


前言

我们首先使用学习到的多线程知识,模拟电影票售票过程,引出线程安全问题。

 /*
    需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口
    请设计一个程序模拟该电影院售票过程。
    使用Thread类实现
    分析:   1.三个窗口相当于三个线程
            2.100张票属于共享资源
     */
  • 代码1:采用多线程实现方式1实现
public class 案例演示1 {
    
    
    public static void main(String[] args) {
    
    
        CellThread th1 = new CellThread("窗口1");
        CellThread th2 = new CellThread("窗口2");
        CellThread th3 = new CellThread("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class CellThread extends Thread{
    
    
    //设置票为静态变量,即共享资源
    static int piao = 100;
    //提供无参构造
    public CellThread() {
    
    
    }
    //创建有参构造,不再调用setName()方法给线程起名字
    public CellThread(String name) {
    
    
        super(name);
    }

    @Override
    public void run() {
    
    
        while (true){
    
    
            if(piao>0){
    
    
                System.out.println(Thread.currentThread().getName()+"正在售卖"+(piao--)+"张票");
            }
        }
    }
}
  • 代码2:采用多线程实现方式2实现
public class 案例演示2 {
    
    
    public static void main(String[] args) {
    
    
        Myrunable myrunable = new Myrunable();
        //我们只创建了一个任务,所以是共同售卖100张票。
        Thread th1 = new Thread(myrunable, "窗口1");
        Thread th2 = new Thread(myrunable, "窗口2");
        Thread th3 = new Thread(myrunable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class Myrunable implements Runnable{
    
    
    static int piao=1000;
    @Override
    public void run() {
    
    
        while (true) {
    
    
            if (piao > 0) {
    
    
                //模拟了一下网络延迟,发现出现不合理的数据,即出现了线程安全问题。
                //1.出现了0票和负数票,是因为线程并发执行导致的
                //2,出现了重复票,是因为原子性所导致的。(自行科普)
                try {
    
    
                    Thread.sleep(50);
                } catch (InterruptedException e) {
    
    
                }
                System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
            }
        }
    }
}

上述代码出现了线程安全问题

  • 线程安全问题产生的条件:
  1. 是多线程环境
  2. 多个线程存在共享资源
  3. 有多条语句在操作共享资源,对共享资源的操作不是原子性操作

一、同步代码块解决线程安全问题

  • 使用同步代码块的格式:
    /*
    我们使用同步代码块,将可能出现线程安全问题的代码包裹起来。
    synchronized (锁对象){
        需要同步的代码
    }

		锁对象:可以是java中的任意一个对象,常new Object()。注意不可以在括号内new对象,这样每个线程持有的不是同一把锁,没有效果。需要将该对象定义为静态成员变量,被线程共享。
		需要同步的代码:可能出现线程安全的问题。(千万不可以出现死循环,这样线程会出现阻塞)
    */
public class 使用同步代码块解决线程安全问题 {
    
    
    public static void main(String[] args) {
    
    
        Myrunable1 myrunable = new Myrunable1();
        Thread th1 = new Thread(myrunable, "窗口1");
        Thread th2 = new Thread(myrunable, "窗口2");
        Thread th3 = new Thread(myrunable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class Myrunable1 implements Runnable {
    
    
    static int piao = 100;
    //注意:多个线程要使用同一把锁
    static Object obj=new Object();
 
    @Override
    public void run() {
    
    
        while (true) {
    
    
            //同步代码块,包裹可能出现线程安全问题的代码块
            //锁对象:可以是java中的任意一个对象,常new Object()
            synchronized (obj){
    
    
                if (piao > 0) {
    
    
                    try {
    
    
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
    
    
                    }
                    System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
                }
            }
            }
    }
}
/*
为什么加了同步代码块就能解决线程安全问题?
    某个线程一旦抢占cpu时间片,进入同步代码块,就会持有该锁。
    其他线程没有该锁,只能等待,无法并发执行。
    当持有锁的线程出了同步代码块,就会释放锁,然后所有线程再次争抢时间片。
    但是加锁,数据安全,效率会降低。
 */

二、同步方法解决线程安全问题

  • 我们可以将出现线程安全的代码抽成方法,并在该方法上加上synchronized,将该方法变成同步方法,这样不需要设置锁对象。
public class 同步方法解决线程安全问题 {
    
    
    public static void main(String[] args) {
    
    
        Myrunable2 myrunable = new Myrunable2();
        Thread th1 = new Thread(myrunable, "窗口1");
        Thread th2 = new Thread(myrunable, "窗口2");
        Thread th3 = new Thread(myrunable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

class Myrunable2 implements Runnable {
    
    
    static int piao = 100;
    @Override
    public void run() {
    
    
        //1.将可能出现线程安全问题的代码抽成方法
            maipiao();
    }

       //2.在方法上加上synchronized,将该方法变成个同步方法
    public synchronized void maipiao(){
    
    
        while (true){
    
    
            if (piao > 0) {
    
    
                try {
    
    
                    Thread.sleep(50);
                } catch (InterruptedException e) {
    
    
                }
                System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
            }
        }
    }
}
/*
我们单独使用同步代码块或同步方法都可以解决线程安全问题,
但是我们混合使用同步代码块和同步方法时候,可能不能解决线程安全问题。
那么是因为同步代码块和同步方法使用的不是同一把锁。
    同步方法使用的默认锁对象是this(当将同步方法定义成静态方法时,锁对象是当前类对象的字节码对象)
    同步代码块使用的锁对象是任意java对象
解决方法:将同步代码块的锁对象与同步方法保持一致。
 */
  • 注意锁对象
  1. 同步代码块的锁对象是任意java对象
  2. 同步方法的锁对象是this
  3. 静态同步方法的锁对象是当前字节码对象
    不要混合使用同步代码块和同步方法,如果使用注意将同步代码块的锁对象与同步方法的锁对象保持一致

三、Lock锁解决线程安全问题

  • JDK5以后提供了一个新的锁对象Lock,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
  • ReentrantLock是其接口的一个实现类
  • void lock() 加锁 void unlock() 释放锁
  • 格式:
/*
    加锁
    lock.lock();
            try{
                可能出现线程安全的代码
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //释放锁
                lock.unlock();
            }

 */
public class jdk15之后lock锁 {
    
    
    public static void main(String[] args) {
    
    
        Myrunable3 myrunable = new Myrunable3();
        Thread th1 = new Thread(myrunable, "窗口1");
        Thread th2 = new Thread(myrunable, "窗口2");
        Thread th3 = new Thread(myrunable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

class Myrunable3 implements Runnable {
    
    
    static int piao = 100;
    //1.创建lock实现类的一个对象
    static Lock lock =new ReentrantLock();
    @Override
    public void run() {
    
    
        while (true) {
    
    
            //2.加锁
            lock.lock();
            try{
    
    
                if (piao > 0) {
    
    
                    try {
    
    
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
    
    
                    }
                    System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
                }
            }catch (Exception e){
    
    
                e.printStackTrace();
            }finally {
    
    
                //3.释放锁
                lock.unlock();
            }


        }
    }
}

猜你喜欢

转载自blog.csdn.net/m0_46988935/article/details/112864455