线程安全问题-----同步机制

线程安全

1.线程安全讲解

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且他的变量的值也和预期的是一样的,就是线程安全.

我们可以通过一个案例,来演示线程安全问题:

电影院要买票,我们模拟电影院买票过程.假设此次电影提供100个座位.

模拟票:

public class Ticket implements Runnable {
    static  int ticket = 100;
    @Override
    public void run() {

        while (true){
            //判断是否还有票可以卖
            if (ticket < 1){
                break;
            }
            //模拟出票时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //模拟出票
          System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
            ticket--;
        }


    }
}

测试类:

public class TicketTest {
    public static void main(String[] args) {
        //创建线程任务对象
        Ticket ticket = new Ticket();
        //创建四个窗口对象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        Thread t4 = new Thread(ticket, "窗口4");

        //同时卖票
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

结果中有这样一部分现象:
在这里插入图片描述
发现程序出现两个问题:

  1. 出售重复票.
  2. 出售不存在的票.

这种问题,几个窗口(线程)票数不同步,这种问题称为线程不安全.

2.线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写操作,就容易出现线程安全问题.

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在的票问题,java中提供了(synchronized)来解决.

根据案例简述:

窗口1线程进入操作的时候,窗口2和窗口3窗口4线程只能在外等着,
窗口1操作结束,窗口1和窗口2和窗口3窗口4才有机会进入代码去执行。
也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,
等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,
保证了数据的同步性,解决了线程不安全的现象。

为了保证每个线程都能正常执行原有操作,java引入了线程同步机制.

3线程安全解决
3.1方式一 -----同步代码块
  • 同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问.

格式:

synchronized(同步锁){
     需要同步操作的代码
}

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象可以是任意类型对象.
  2. 多个线程对象要使用同一个锁对象.

使用同步代码块解决代码:

public class Ticket implements Runnable {
    static  int ticket = 100;
    static Object obj = new Object();
    @Override
    public void run() {

        while (true) {
            synchronized (obj) {
                //判断是否还有票可以卖
                if (ticket < 1) {
                    break;
                }
                //模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //模拟出票
                System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

结果:

在这里插入图片描述

使用了同步代码块后,上述线程安全问题,解决了.

  • 证明同步锁法法锁对象

  • 
    /**
     * @author:donjor
     */
    public class Demo {
        public  void method1(){// abcdefg
            // 同步代码块
            synchronized (this){
                System.out.print("a");
                System.out.print("b");
                System.out.print("c");
                System.out.print("d");
                System.out.print("e");
                System.out.print("f");
                System.out.print("g");// 线程B抢走了
                System.out.println();
            }
        }
    
        // 非静态同步方法
        public synchronized  void method2(){// 123456
            System.out.print("1");
            System.out.print("2");
            System.out.print("3");
            System.out.print("4");
            System.out.print("5");
            System.out.print("6");
            System.out.println();
        }
    
        public static void method3(){// abcdefg
            // 同步代码块
            synchronized (Demo.class){
                System.out.print("a");
                System.out.print("b");
                System.out.print("c");
                System.out.print("d");
                System.out.print("e");
                System.out.print("f");
                System.out.print("g");// 线程B抢走了
                System.out.println();
            }
        }
    
        // 静态同步方法
        public synchronized static   void method4(){// 123456
            System.out.print("1");
            System.out.print("2");
            System.out.print("3");
            System.out.print("4");
            System.out.print("5");
            System.out.print("6");
            System.out.println();
        }
    }
    
    
    
    
    /**
     * @author:donjor
     */
    public class Test1Demo {
        public static void main(String[] args) {
            /*
                证明同步方法的锁对象:
                    静态同步方法: 该同步方法所在的类的字节码文件对象 即类名.class
                    非静态同步方法: 锁对象this
    
                多线程保证数据安全条件:
                        1.对执行相同的代码进行加锁
                        2.多个线程的锁对象要一致
    
                 场景:
                    一个线程使用的是同步代码块对资源加锁
                    一个线程使用的是同步方法对资源加锁
    
                    同步代码块与同步方法的锁对象要一致,才能锁的住
    
             */
            // 创建一个Demo对象
            Demo demo = new Demo();
    
           /* // 创建一个线程A执行method1方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 调用method1方法
                    while (true){
                        demo.method1();
                    }
                }
            },"线程A").start();
    
            // 创建一个线程B执行method2方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 调用method2方法
                    while (true) {
                        demo.method2();
                    }
                }
            },"线程B").start();*/
    
            // 创建一个线程A执行method1方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 调用method3方法
                    while (true){
                        demo.method3();
                    }
                }
            },"线程A").start();
    
            // 创建一个线程B执行method2方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 调用method4方法
                    while (true) {
                        demo.method4();
                    }
                }
            },"线程B").start();
    
        }
    }
    
3.2方式二 -----同步方法
  • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在外等待.

格式:

public synchronized void method(){
   	可能会产生线程安全问题的代码
}

使用同步方法代码如下:

public class Ticket implements Runnable {
    static  int ticket = 100;
    static Object obj = new Object();
    @Override
    public void run() {

        while (true) {
            if (sellTicket()) break;
        }
    }

    private synchronized boolean sellTicket() {
        //判断是否还有票可以卖
        if (ticket < 1) {
            return true;
        }
        //模拟出票时间
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //模拟出票
        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
        ticket--;
        return false;
    }
}

注意:

  • 静态同步方法锁对象: 类名.class
  • 非静态同步方法锁对象:this
3.3方式三 -----Lock锁

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock():加同步锁。
  • public void unlock():释放同步锁。

使用如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
    static  int ticket = 100;
    Lock lock = new ReentrantLock();
    @Override
    public void run() {

        while (true) {

                lock.lock();
                //判断是否还有票可以卖
                 if (ticket > 0) {
                    break;
                }
                //模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //模拟出票
                System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                ticket--;
                lock.unlock();
            if (ticket < 1) {
                    break;
                }

        }
    }
}
总结

同步机制:

  • 同步代码块

    • 应用场景:当需要对方法中部分代码进行加锁,那么就可以使用同步代码块

    • 格式

      • synchronized(锁对象){
        
        }
        
        
      • 锁对象:

        • 可以是任意对象
        • 多条线程执行相同的代码,要保证数据安全的话,就需要保证所有线程的锁对象一致
  • 同步方法

    • 应用场景:当需要对一个方法中所有的代码进行加锁,那么就使用同步方法
    • 格式: 在返回值类型前面加 synchronized
    • 锁对象
      • 静态同步方法锁对象: 类名.class
      • 非静态同步方法锁对象:this
  • Lock锁

    • public void lock():加同步锁。
    • public void unlock():释放同步锁。

,那么就可以使用同步代码块

  • 格式

    • synchronized(锁对象){
      
      }
      
      
    • 锁对象:

      • 可以是任意对象
      • 多条线程执行相同的代码,要保证数据安全的话,就需要保证所有线程的锁对象一致
  • 同步方法

    • 应用场景:当需要对一个方法中所有的代码进行加锁,那么就使用同步方法
    • 格式: 在返回值类型前面加 synchronized
    • 锁对象
      • 静态同步方法锁对象: 类名.class
      • 非静态同步方法锁对象:this
  • Lock锁

    • public void lock():加同步锁。
    • public void unlock():释放同步锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值