zoukankan      html  css  js  c++  java
  • 廖雪峰Java11多线程编程-2线程同步-2synchronized方法

    1.对Java方法进行加锁

    Java使用synchronized对一个对象进行加锁,也就可使用synchronized对一个方法进行加锁。
    在执行synchronized语句时,我们首先获得synchronized指定的锁。当我们添加synchronized语句块的时候,首先需要注意的是锁住那个对象?让线程自己决定锁住哪个对象,不是一个好的设计。更好的方法是通过数据封装,把同步逻辑封装到持有数据的实例中。

    1.1 同步方法

    由于多线程访问实例变量的时候,可能会出现同步问题,所以在add和dec方法中把this对象加锁,这样就可以保证对任何一个count实例,多线程访问的时候都是安全的。当我们对this进行加锁的时候,我们可以用synchronized来修饰这个方法,这样就把同步的代码块变为方法级别。
    用synchronized修饰的方法相当于在方法内部用synchronized锁住this变量,这2种方法是完全等价的

    注意:如果方法内的语句不在同步代码块内,就不再等价。

    1.2 静态方法:锁住的是Class实例

    如果对静态方法使用synchronized修饰符,锁住的将是当前class的Class实例。

    2 示例

    class Counter{
        int count = 0;
        public synchronized void add(int n){
            count += n;
        }
        public synchronized void dec(int n){
            count -= n;
        }
        public int get(){//读取一个int类型是原子操作,不需要同步
            return count;
        }
    }
    class AddThread extends Thread {
        Counter counter;
    
        public AddThread(Counter counter) {
            this.counter = counter;
        }
    
        public void run() {
            for (int i = 0; i < Main.LOOP; i++) {
                counter.add(1);
            }
        }
    }
    class DecThread extends Thread{
        Counter counter;
        public DecThread(Counter counter){
            this.counter = counter;
        }
        public void run(){
            for(int i=0;i<Main.LOOP;i++){
                counter.dec(1);
            }
        }
    }
    
    public class Main{
        static final int LOOP=10000;
        public static void main(String[] args) throws InterruptedException{
            Counter counter  = new Counter();
            Thread t1 = new AddThread(counter);
            Thread t2 = new DecThread(counter);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(counter.get());
        }
    }
    

    3.读取方法是否需要同步?

    在这个例子中,value是int类型,读取int类型是原子操作,是不需要同步的。

        public int get(){ //读取一个int类型是原子操作,不用同步
            return count;
        }
    

    但是把代码改一下,把value变成一个长度为2的int数组。就会发现,读方法仍然需要同步。否则在执行完result[0]=this.value[0]时,如果其他线程改变了value数组,会导致读取的结果不一致,所以读方法通常也需要同步。

        public synchronized int[] get(){
            int[] result = new int[2];
            result[0] = this.value[0];
            result[1] = this.value[1];//读取完result[0],如果其他线程改变了result[1]的值,会导致读取的结果不一致。
            return result;
        }
    

    4.线程安全与非线程安全

    4.1线程安全

    如果一个类被设计为允许多线程正确访问, 这个类就是“线程安全”的(thread-safe),如java.lang.StringBuffer,其方法全部是用synchronized修饰的。

    线程安全的类:

    • 不变类:String,Integer,LocalDate。不变类因为一旦创建,实例内部的成员变量无法改变,所以多线程只能读,不能写,不需要同步,就是安全的。
    • 没有成员变量的类:Math,这些工具类只提供了静态方法,没有成员变量。所以也是线程安全的。
    • 正确使用synchronized的类:StringBuffer

    4.2 其他的类都是非线程安全的类:

    • 不能在多线程中共享实例并修改:ArrayList
    • 可以在多线程中以只读方式共享

    5.总结:

    • 用synchronized修饰方法可以把整个方法变为同步代码块
    • synchronized方法加锁对象是this
    • 通过合理的设计和数据封装可以让一个类变为线程安全
    • 一个类没有特殊说明,默认不是thread-safe
    • 多线程能否访问某个非线程安全的实例,需要具体问题具体分析
  • 相关阅读:
    【Nginx】使用Nginx做反向代理时,关于被代理服务器相应的超时设置
    【Quartz】配置最简单的集群
    【Quartz】将定时任务持久化到数据库
    【Quartz】Quartz的搭建、应用(单独使用Quartz)
    【Linux】用grep在文档中查找内容
    【Linux】方便的SecureCRT文件上传、下载命令
    【MySQL】MySQL复制表结构、表数据
    【Linux】vi(vim)起步学起来有些困难,一步一步温习
    【MySQL】MySQL PLSQL Demo
    【Linux】VMware中为CentOS设置静态IP(非动态获取IP)
  • 原文地址:https://www.cnblogs.com/csj2018/p/11001483.html
Copyright © 2011-2022 走看看