• Java线程之synchronized


    翻译:https://www.journaldev.com/1061/thread-safety-in-java
    在这里插入图片描述
    我们知道线程安全在Java中是一个非常重要的主题,当多个线程操作共享数据时,如果没有任何控制,将会产生数据不一致的问题。数据不一致的重要原因是,当更新一个变量的值时,是分三步完成的,第一获取变量值,第二对变量值进行更新,第三将修改后的变量值刷新到内存。

    下面我们用一个简单程序来验证一下这个问题,多线程去更新共享数据:

    package com.lkf.mulithread;
    
    public class ThreadSafety {
        public static void main(String[] args) throws InterruptedException {
    
            ProcessingThread pt = new ProcessingThread();
            Thread t1 = new Thread(pt, "线程1");
            t1.start();
            Thread t2 = new Thread(pt, "线程2");
            t2.start();
            //wait for threads to finish processing
            t1.join();
            t2.join();
            System.out.println("执行次数:" + pt.getCount());
        }
    
        static class ProcessingThread implements Runnable {
            private int count;
    
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    processSomething(i);
                    count++;
                }
            }
    
    
            private void processSomething(int i) {
                // processing some job
                try {
                    Thread.sleep(i * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            public int getCount() {
                return this.count;
            }
    
        }
    }
    
    

    当多次执行之后,我们会发现,最终的结果是6或7或8,而不是我们的预期结果8,出现这种情况的原因就在于count++

    保证线程安全

    针对多线程环境下,线程安全问题,有以下几种方法来解决:

    1.关键字Synchronization是使用最简单应用最广泛的确保线程安全的方式

    2.使用原子包装类,位于java.util.concurrent.atomic包下,比如:AutomicInteger

    3.使用锁,位于java.util.concurrent.locks包下

    4.使用线程安全集合类,比如:ConcurrentHashMap

    5.使用volatile关键字保证线程每次直接从内存中读取变量值,而不是从工作内存中读取

    Synchronized

    • synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性.
    • synchronized有两种使用方式,一种是锁定方法,另一种是锁定代码块
    • 普通同步方法,锁的是当前实例对象,静态同步方法,锁的是当前类,因此最好的方法是,只锁定需要同步的代码块
    • synchronized(this)锁定是当前对象,因为使用synchronized来保证线程安全具有一定的性能成本,因此建议在必要的时候使用

    下面是推荐的保证线程安全的使用方式

        //虚拟对象
        private Object mutex=new Object();
        ...
        //using synchronized block to read, increment and update count value synchronously
        synchronized (mutex) {
                count++;
        }
    

    事例分析

    下面我们一起分析一些保证线程安全的一些事例:

    事例1

    public class MyObject {
     
      // 锁定对象的监视器
      public synchronized void doSomething() { 
        // ...
      }
    }
     
    // Hackers code
    MyObject myObject = new MyObject();
    synchronized (myObject) {
      while (true) {
        // 无限期循环
        Thread.sleep(Integer.MAX_VALUE); 
      }
    }
    

    注意,同步代码试图锁定myObject实例,一旦它获得了锁,它就不会释放它导致doSomething()方法阻塞等待锁,这会导致系统陷入死锁并导致Denial of Service (DoS)

    事例2

    public class MyObject {
      public Object lock = new Object();
     
      public void doSomething() {
        synchronized (lock) {
          // ...
        }
      }
    }
    
    //untrusted code
    
    MyObject myObject = new MyObject();
    //change the lock Object reference
    myObject.lock = new Object();
    

    注意,锁对象是公共的,通过更改它的引用,我们可以在多个线程中执行同步块并行。这种方法是可行的,如果是私有锁对象,可以通过setter方法来修改它的引用

    事例3

    package com.lkf.mulithread;
    
    import java.util.Arrays;
    
    public class SyncronizedMethod {
        public static void main(String[] args) throws InterruptedException {
            String[] arr = {"我是1号", "我是2号", "我是3号", "我是4号", "我是5号", "我是6号"};
            HashMapProcessor hmp = new HashMapProcessor(arr);
            Thread t1 = new Thread(hmp, "线程t1");
            Thread t2 = new Thread(hmp, "线程t2");
            Thread t3 = new Thread(hmp, "线程t3");
            long start = System.currentTimeMillis();
            //start all the threads
            t1.start();
            t2.start();
            t3.start();
            //wait for threads to finish
            t1.join();
            t2.join();
            t3.join();
            System.out.println("Time taken= " + (System.currentTimeMillis() - start));
            //check the shared variable value now
            System.out.println(Arrays.asList(hmp.getMap()));
        }
    
    
        static class HashMapProcessor implements Runnable {
    
            private String[] strArr = null;
    
            public HashMapProcessor(String[] m) {
                this.strArr = m;
            }
    
            public String[] getMap() {
                return strArr;
            }
    
            @Override
            public void run() {
                processArr(Thread.currentThread().getName());
            }
    
            private void processArr(String name) {
                for (int i = 0; i < strArr.length; i++) {
                    //process data and append thread name
                    processSomething(i);
                    addThreadName(i, name);
                }
            }
    
            private void addThreadName(int i, String name) {
                strArr[i] = strArr[i] + ":" + name;
            }
    
            private void processSomething(int index) {
                // processing some job
                try {
                    Thread.sleep(index * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    
    

    输出结果:

    Time taken= 15015
    [我是1号:线程t1:线程t2:线程t3, 
    我是2号:线程t1:线程t2:线程t3, 
    我是3号:线程t1:线程t2:线程t3, 
    我是4号:线程t1, 
    我是5号:线程t2, 
    我是6号:线程t3:线程t2]
    
    

    可以看到每个线程中数组数据不完整,是因为共享数据没有同步造成的

    下面我们修改一下代码,来保证线程安全:

    private Object lock = new Object();
        private void addThreadName(int i, String name) {
            synchronized(lock){
            strArr[i] = strArr[i] +":"+name;
            }
        }
    

    输出结果:

    Time taken= 15015
    [我是1号:线程t1:线程t2:线程t3, 
    我是2号:线程t1:线程t3:线程t2, 
    我是3号:线程t1:线程t3:线程t2, 
    我是4号:线程t1:线程t3:线程t2, 
    我是5号:线程t3:线程t2:线程t1, 
    我是6号:线程t3:线程t1:线程t2]
    
    
  • 相关阅读:
    VSCode多文件编译执行
    python当函数赋给变量时带括号与不带括号的区别
    VS添加现有的文件夹到项目中
    pythonpython中a=a+b与a+=b的区别
    android创建无界面的服务运行在后台
    androidService_Intent_must_be_explicit的解决方法
    VSCode配置c_cpp环境
    VSCode切换焦点时自动保存文件
    Gradle动态移除清单文件中的权限
    C++多线程
  • 原文地址:https://www.cnblogs.com/liukaifeng/p/10052660.html
Copyright © 2020-2023  润新知