• Java中的synchronized关键字


    Java中的多线程同步:

    讨论synchronized之前先看简单看一些java中的多线程同步。

    当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。比如,有个初始银行账户Account、其初始值为0,有两个小伙伴AB去银行的两个窗口同时往Account这个账户存钱100¥,这个时候需要先获取账户原来的值,然后在原始值的基础上加上当前要存入的金额,再写回记账本。AB拿到的初始值都是0,再加上要存入的钱后总额为100元,再写回记账本,因此,最终记账本的显示的账户金额是100,这和实际情况就有出入了。

    没有进行线程同步的实例代码:

    public class Test {
       
    private static int value = 0;
        public static int
    getValue() {
           
    return value;
       
    }

       
    public void add(){
           
    int tmp = value;
            try
    {
                Thread.sleep(
    10);
           
    } catch (InterruptedException e) {
                e.printStackTrace()
    ;
           
    }
           
    value = tmp + 100;
       
    }
    }

     

    public class ThreadMain implements Runnable {
        private int id;
        private Test test;
        public ThreadMain(int id, Test test) {
            this.id = id;
            this.test = test;
        }
        public void run() {
            test.add();//d对数据的操作没有进行线程同步,不安全,最终结果不可预测
            System.out.println("线程" + this.id + "完成 add操作");
        }
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[5];
            Test test = new Test();
            for (int i = 0; i < 5; i++) {
                Thread thread = new Thread(new ThreadMain(i, test));
                threads[i] = thread;
                thread.start();
            }
            for(int i = 0; i < 5; i++){
                Thread thread = threads[i];
                try {
                    thread.join();
                }catch (Exception e){
    
                }
            }
            System.out.print("value= " + Test.getValue());
        }
    }

    运行结果:

    线程1完成 add操作

    线程4完成 add操作

    线程3完成 add操作

    线程0完成 add操作

    线程2完成 add操作

    value= 100

     

    可以看到,在没有进行线程同步的情况下,多个线程会同时操作同一个数据、对象,导致结果不可预知。

    我们需要的逻辑应该是这样的:当A在进行获取账户初始、进行账户增加、最终写回记账本这个存钱过程中,B不能同时进行操作,B等待A完成存钱动作后,B可以开始存钱的动作;因此需要一种机制,确保在任何时候,只有一个人在进行存钱的动作。在java中,使用synchronized关键字是实现该功能的一种方法(包括volatile关键字、Lock方式也可以实现)

    Synchronized关键字:

    synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 
    1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
    2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
    3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
    4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

    我们使用同步代码块的方式,来看看上述存钱这件事用线程同步的方式应该怎么做:

    public class Test {
        private static int value = 0;
        public static int getValue() {
            return value;
        }
    
        public  void addWithSyn(){
            synchronized(this) {
                int tmp = value;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                value = tmp + 100;
            }
        }
    }
    public class ThreadMain implements Runnable {
        private int id;
        private Test test;
        public ThreadMain(int id, Test test) {
            this.id = id;
            this.test = test;
        }
        public void run() {
            test.addWithSyn();//
            System.out.println("线程" + this.id + "完成 add操作");
        }
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[5];
            Test test = new Test();
            for (int i = 0; i < 5; i++) {
                Thread thread = new Thread(new ThreadMain(i, test));
                threads[i] = thread;
                thread.start();
            }
            for(int i = 0; i < 5; i++){
                Thread thread = threads[i];
                try {
                    thread.join();
                }catch (Exception e){
    
                }
            }
            System.out.print("value= " + Test.getValue());
        }
    }

    运行结果:

    线程1完成 add操作

    线程0完成 add操作

    线程4完成 add操作

    线程3完成 add操作

    线程2完成 add操作

    value= 500

     

    当然,也可以对方法同步、对类对象同步,由于某个线程进入同步的代码域后,其它线程要想进入同步的代码域,就要进入阻塞状态,会降低程序的性能。因此,应根据需要进行线程同步。

    被synchronized修饰的代码称作临界区,当一个线程a进入该对象的临界区A时,其它试图进入临界区A或者该对象的其它临界区时,将会进入阻塞状态,直至线程a访问完临界区,其它线程才有机会进入临界区。

    在使用临界区保护代码块时,必须将对象的引用作为传入参数,通常使用this来引用执行方法所属的这个对象,如上述代码所示。但是,当对象中有两个非依赖属性时,

    我们需要同步每一个变量的访问,即同一时刻只允许一个线程访问属性A,但是其他线程仍然可以访问属性B。

    举个例子:

    有一个对象Bank,管理用户两张银行卡账户A和B,同一个时刻,对A账户只能执行存钱或者取钱中的一种行为,但是

    对A账户操作的同时,还必须允许能够同时操作B:

    银行账户类Bank:

    public class Bank {
       
    private double accountA;
        private final
    Object accountControlA;
        private double
    accountB;
        private final
    Object accountControlB;

        public
    Bank(double accountA, double accountB) {
           
    this.accountA = accountA;
            this
    .accountB = accountB;
           
    accountControlA = new Object();
           
    accountControlB = new Object();
       
    }

       
    public void addAccountA(double money) {
           
    synchronized (accountControlA) {
               
    double tmp = accountA;
                try
    {
                    Thread.sleep(
    10);
               
    } catch (InterruptedException e) {
                    e.printStackTrace()
    ;
               
    }
               
    accountA = tmp + money;
           
    }
        }

       
    public void addAccountB(double money) {
           
    synchronized (accountControlB) {
               
    double tmp = accountB;
                try
    {
                    Thread.sleep(
    10);
               
    } catch (InterruptedException e) {
                    e.printStackTrace()
    ;
               
    }
               
    accountB = tmp + money;
           
    }
        }

       
    public double getAccountA() {
           
    return accountA;
       
    }

       
    public double getAccountB() {
           
    return accountB;
       
    }

    }

    两个线程类,分别对账户accountA和accountB进行操作:

    public class ThreadObjA implements Runnable {
       
    private int id;
        private
    Bank bank;

        public
    ThreadObjA(int id, Bank bank) {
           
    this.id = id;
            this
    .bank = bank;
       
    }

       
    public void run() {
           
    bank.addAccountA(100.0);//
           
    System.out.println("线程ThreadObjA" + this.id + "完成 add操作");
       
    }
    }

    public class ThreadObjB implements Runnable {
       
    private int id;
        private
    Bank bank;

        public
    ThreadObjB(int id, Bank bank) {
           
    this.id = id;
            this
    .bank = bank;
       
    }

       
    public void run() {
           
    bank.addAccountB(100.0);//
           
    System.out.println("线程ThreadObjB" + this.id + "完成 add操作");
       
    }
    }

    各创建五个线程来对这两个账户进行操作:

    public static void main(String[]args){
        Bank bank =
    new Bank(0.0,100.0);
       
    Thread[] threads = new Thread[10];
        for
    (int i = 0; i < 5; i++) {
            Thread thread =
    new Thread(new ThreadObjA(i, bank));
           
    threads[i] = thread;
           
    thread.start();
       
    }
       
    for (int i = 0; i < 5; i++) {
            Thread thread =
    new Thread(new ThreadObjB(i, bank));
           
    threads[i+5] = thread;
           
    thread.start();
       
    }
       
    for(int i = 0; i < 10; i++){
            Thread thread = threads[i]
    ;
            try
    {
                thread.join()
    ;
           
    }catch (Exception e){

            }
        }
        System.
    out.print("bank accountA=" + bank.getAccountA() + " ");
       
    System.out.print("bank accountB=" + bank.getAccountB() + " ");
    }

    由于synchronized传入的对象不同,JVM保证同一时间只有一个线程能够访问这个对象的代码保护块(临界区),因此ThreadObjA类的线程和ThreadObjB类的线程是可以同时运行访问各自的代码块的。但是,同一个时刻ThreadObjA类的多个线程线程只允许有一个访问对象Bank中accountControlA对象的代码保护块。

    Java中多线程同步的其他几种方式:

    使用特殊域变量(Volatile)实现线程同步。

    使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象。

    这两种方式都一定的限制,其中Lock对象需要使用者手动释放锁。官方推荐使用synchronized来实现多线程同步,因此本人学习重点还是放在synchronized关键字上。

    参考博客:JavaSynchronized的用法

     

  • 相关阅读:
    GRIDVIEW导出到EXCEL
    数据表示:字节 高位低位
    js学习笔记0
    12奇招,循序删除顽固的文件
    加快开关机速度
    PHP正则表达式的快速学习
    firefox下height不能自动撑开的解决办法
    给MySQL加密(也适用于Wamp5中)
    我的电脑创建资源管理器
    css 圆角7种CSS圆角框解决方案
  • 原文地址:https://www.cnblogs.com/cl1024cl/p/6205016.html
Copyright © 2020-2023  润新知