• Java Thread系列(三)线程安全


    Java Thread系列(三)线程安全

    一、什么是线程安全

    线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。

    线程安全来说,需要满足以下两个特性:

    • 原子性

    • 可见性

    public class MyThread extends Thread {
    
        private int count = 5;
    
        //synchronized加锁 同步锁
        public /*synchronized*/ void run () {
            System.out.printf("线程%s执行,count:%s
    ", this.currentThread().getName(),--count);
        }
    
        public static void main(String[] args) {
            /**
             * 当多个线程访问 MyThread 的 run 方法时,以排队的方式进行处理(这里的排序是按照 CPU 分配的先后顺序而定的)
             * 一个线程要执行 synchronized 修饰的方法时
             *  1. 尝试获得锁,如果拿到锁则执行该方法
             *  2. 这个线程就会不断尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
             */
            MyThread thread = new MyThread();
            new Thread(thread, "t1").start();
            new Thread(thread, "t2").start();
            new Thread(thread, "t3").start();
            new Thread(thread, "t4").start();
            new Thread(thread, "t5").start();
            new Thread(thread, "t6").start();
        }
    }
    

    执行结果:

    //MyThread 的 run() 方法不加锁 synchronized
    线程t1执行,count:4
    线程t4执行,count:1
    线程t5执行,count:1
    线程t3执行,count:2
    线程t2执行,count:3
    
    //run() 加锁 synchronized,期待的结果 
    线程t1执行,count:4
    线程t2执行,count:3
    线程t5执行,count:2
    线程t4执行,count:1
    线程t3执行,count:0
    

    由此可见:

    1. 多个线程要执行 synchronized 修饰的方法时,必须获取对象锁,如果得不到这把锁,就处于等待状态,直到获取这把锁才执行这个方法。

    二、什么是锁:对象锁和类锁

    多个线程多个锁,多个线程,每个线程都可以拿到自己指定的锁,分别获得锁后执行 synchronized 修辞的方法。

    • 同步(synchronized)的概念就是共享,我们要牢牢记住“共享”这两个字,如果不是共享的资源,就没有必要进行同步。

    • 异步(asynchronized)的概念就是独立,相互之间不受任何制约。eg: Ajax

    public class MutiThread {
    
        private /*static*/ int num = 0;
    
        /**
         * synchronized:对象锁,两个对象,线程获得的就是两个不同的锁,互不影响
         * static synchronized:表示类级别的锁,即便多个对象也是相同的锁
         */
        public /*static*/ synchronized void printNum (String tag) {
            try {
                if("a".equalsIgnoreCase(tag)) {
                    System.out.printf("tag %s 设置成功
    ", tag);
                    num = 100;
                    Thread.sleep(1000);
                } else {
                    System.out.printf("tag %s 设置成功
    ", tag);
                    num = 200;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.printf("tag %s , num=%s
    ", tag, num);
        }
    
        public static void main(String[] args) {
            
            final MutiThread m1 = new MutiThread();
            final MutiThread m2 = new MutiThread();
            new Thread(new Runnable() {
                public void run() {
                    m1.printNum("a");
                }
            }).start();
            new Thread(new Runnable() {
                public void run() {
                    m1.printNum("b");
                }
            }).start();
        }
    }
    

    执行结果:

    //run()方法用synchronized修辞
    tag b 设置成功
    tag b , num=200
    tag a 设置成功
    tag a , num=100
    
    //run()方法用static synchronized修辞
    tag a 设置成功
    tag a , num=100
    tag b 设置成功
    tag b , num=200
    

    由此可见:

    1. static synchronized 修辞的方法,属于类级别的锁,多个对象共享同一把锁。

    2. synchronized 修辞的方法,属于对象锁,一个对象一把锁。

    三、脏读

    对于对象的同步和异步的方法,一定要考虑问题的整体性,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirtyread)

    public class DirtyRead {
    
        private String username;
        private String password;
    
        public /*synchronized*/ void setValue(String username, String password) {
            this.username = username;
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
            this.password = password;
            System.out.printf("username=%s; password=%s
    ", username, password);
        }
    
        public /*synchronized*/ void getValue() {
            System.out.printf("username=%s; password=%s
    ", username, password);
        }
    
        public static void main(String[] args) throws InterruptedException {
            final DirtyRead dirtyRead = new DirtyRead();
            new Thread(new Runnable() {
                public void run() {
                    dirtyRead.setValue("admin", "admin");
                }
            }).start();
            Thread.sleep(1000);
            dirtyRead.getValue();
            //username=admin; password=null 不加锁,产生脏读
            //username=admin; password=admin 加锁
        }
    }
    

    执行结果:

    1. setValue() 执行要 2s,而主程序 1s 时调用 getValue() ,这时username 已经赋值,而 password 仍未赋值,产生了脏读,username=admin; password=null

    2. setValue() 和 getValue() 都对 username 和 password 操作,所以要避免产生脏读,需要对这两个方法都加锁 synchronized。


    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    带字数限制提示的输入框效果
    js快速获取数组中的最大值最小值
    js实现连线题
    js自定义图片提示效果
    为了遇见你
    为了明天(励志篇)
    你为什么总是不理我
    爱情,是我一生中最虔诚的信仰
    你是我心中永远抹不掉的痛
    爱你一万次够不够
  • 原文地址:https://www.cnblogs.com/binarylei/p/8999677.html
Copyright © 2020-2023  润新知