• Java 线程安全 与 锁


    Java 线程安全 与 锁

    多线程内存模型

    • 线程私有栈内存
      • 每个线程 私有的内存区域
    • 进程公有堆内存
      • 同一个进程 共有的内存区域

    为什么会有线程安全问题?

    • 多个线程同时具有对同一资源的操作权限,又发生了同时对该资源进行读取、写入的情况,那么就会出现重复操作的情况

    如何解决线程安全问题呢? 加锁

    什么是锁?

    锁就是对于操作资源的一种权限

    锁可以做什么?

    对于一个资源加锁后,每次只能有一个线程对该资源进行操作,当该线程操作结束后,才会解锁。
    解锁之后,所有的线程获得竞争此资源的机会。

    什么情况下需要加锁?

    • 读读 不需要加锁
    • 写写 需要加锁
    • 读写 需要加锁

    加锁的两种方式(synchronized关键字与Lock对象)

    第一种:synchronized关键字

    • 方法前加synchronized关键字

      • 功能:线程进入用synchronized声明的方法时就上锁,方法执行完自动解锁,锁的是当前类的对象
      • 调用synchronized声明的方法一定是排队运行的
      • 当A线程 调用object对象的synchronized声明的X方法时
        • B线程可以调用其他非synchronized声明的方法
        • B线程不能调用其他synchronized声明的非X方法
    • synchronized锁重入

      • 锁重入的概念:自己可以重复获得自己的内部锁。即synchronized声明的方法,可以调用本对象的其他synchronized方法。
      • 锁重入支持继承的环境,即子类的synchronized方法也可以调用父类的synchronized方法。
    • synchronized同步代码块

      • synchronized关键字与synchronized代码块的区别

        • synchronized声明的方法是将当前对象作为锁
        • synchronized代码块是将任意对象作为锁
      • 当两个线程访问同一个对象的synchronized代码块时,只有一个线程可以得到执行,另一个线程只能等待当前线程执行完才能执行。

        • 一半同步,一半异步
          • 不在synchronized代码块中就是异步执行,在synchronized代码块中就是同步执行

    下面对“一半同步,一半异步”进行代码验证

    • 创建项目ltl0002 ,文件Task的代码如下:
    package ltl0002;
    
    public class Task {
    
        public void doTask(){
            for (int i = 0; i < 100; i++) {
                System.out.println("no synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1));
            }
            synchronized (this){
                for (int i = 0; i < 100; i++) {
                    System.out.println("synchronized ThreadName = " + Thread.currentThread().getName() + " i = " + (i+1));
                }
            }
            
        }
    }
    
    • 两个线程类代码
    package ltl0002;
    
    public class MyThread1 implements Runnable{
    
        private Task task = new Task();
    
        public MyThread1(Task task){
            this.task = task;
        }
    
        @Override
        public void run() {
    
            task.doTask();
        }
    }
    
    package ltl0002;
    
    public class MyThread2 implements Runnable{
    
        private Task task = new Task();
    
        public MyThread2(Task task){
            this.task = task;
        }
    
        @Override
        public void run() {
    
            task.doTask();
        }
    }
    

    文件Run.java代码如下:

    package ltl0002;
    
    public class Run {
        public static void main(String[] args) {
            Task task = new Task();
            MyThread1 myThread1 = new MyThread1(task);
            MyThread2 myThread2 = new MyThread2(task);
            Thread tr1 = new Thread(myThread1);
            Thread tr2 = new Thread(myThread2);
            tr1.start();
            tr2.start();
        }
    
    }
    

    程序运行结果如图所示
    image

    进入synchronized代码块之后,排队运行,运行结果如图所示
    image

    在第一张图我们可以看到,线程0 和 1交叉输出,说明是异步进行,而在第二张图可以看出线程0运行完之后,线程1才运行,说明它们是同步运行,验证完毕。

    • 现有三个线程,线程一对num进行修改,线程二三对num进行读取,如何可以实现,线程一与线程二三同步执行,而线程二三异步执行呢?
      现在创建项目ltl0003进行测试,Number文件代码如下
    package ltl0003;
    /**
     * @author liTianLu
     * @Date 2022/4/23 15:53
     * @purpose 成员变量有int num,以及get set方法
     */
    public class Number {
      private int num;
      private boolean change = false;
    
      public int getNum() {
        return num;
      }
    
      public void setNum(int num) {
        this.num = num;
      }
      public boolean isChangeing(){
        return change;
      }
    
      public void setChange(boolean change) {
        this.change = change;
      }
    }
    

    两个线程类的代码如下:

    package ltl0003;
    /**
     * @author liTianLu
     * @Date 2022/4/23 15:36
     * @purpose 更改num的值
     */
    public class MyThread01 implements Runnable{
      static int num = 0;
      Number number;
      public MyThread01(Number num ){
        this.number = num ;
      }
      @Override
      public void run() {
        synchronized (this){
          number.setChange(true);
          for (int i = 0; i < 10000; i++) {
            number.setNum(num++);
          }
          number.setChange(false);
        }
      }
    }
    
    
    package ltl0003;
    
    import static java.lang.Thread.sleep;
    /**
     * @author liTianLu
     * @Date 2022/4/23 15:35
     * @purpose 读取num的值
     */
    public class MyThread02 implements Runnable{
      Number number;
    
      public MyThread02(Number num ){
        this.number = num ;
      }
    
      @Override
      public void run() {
        for (int i = 0; i < 1000 ; i++) {
          //如果number正在更改,就休眠1ms
          while(number.isChangeing()){
            try {
              sleep(1);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
          System.out.println(Thread.currentThread().getName()+"的输出为: num = " + number.getNum());
        }
      }
    
    }
    

    主函数文件Run代码如下:

    package ltl0003;
    /**
     * @author liTianLu
     * @Date 2022/4/23 15:15
     * @purpose 解决锁问题 线程一对num进行修改,线程二三对num进行读取,此代码要实现:线程一与线程二三同步执行,而线程二三异步执行。
     */
    public class Run {
      public static void main(String[] args) {
        Number number = new Number();
        number.setNum(0);
        MyThread01 myThread01 = new MyThread01(number);
        MyThread02 myThread02 = new MyThread02(number);
        Thread tr1 = new Thread(myThread01);
        Thread tr2 = new Thread(myThread02);
        Thread tr3 = new Thread(myThread02);
        tr1.start();
        tr2.start();
        tr3.start();
      }
    }
    

    实验结果如图所示

    image

    我们发现,线程2/3执行的时候,线程1已经执行完毕,且线程2、3异步进行。

    第二种:Lock对象的使用

    • ReentrantLock类可以达到与synchronized同样的效果。
    • 用法:
    ReentrantLock lock = new ReentrantLock (); 
    lock.lock();//加锁
    lock.unlock();//解锁
            
    //使用try catch finally 可以确保finally 中的代码执行,在finally中解锁
    try{
        while(true){
            lock.lock ();
            //操作代码
        }
    }catch (Exception e) {
        e.printStackTrace();
    }finally {
        lock.unlock ();
    }
    
  • 相关阅读:
    Python实用记录(十四):python统计某个单词在TXT/JSON文件中出现的次数
    电力电子转战数字IC20220727day57——寄存器模型(续)
    SQL教程之 开始学习用于数据分析的的五个SQL 命令 (教程含源码)
    互联网摸鱼日报(2022-11-27)
    QT(超详细从0开始)
    电商新趋势:阿里巴巴1688.item_password API引领智能分享新风尚
    CSS超出两行隐藏
    【MySQL】索引和事务(B树、B+树图解原理)
    C++-开源日志库-easylogging-配置-格式记录-线程安全
    java毕业设计校园招聘系统设计mybatis+源码+调试部署+系统+数据库+lw
  • 原文地址:https://www.cnblogs.com/classicltl/p/16183317.html