多线程共享变量问题

非线程安全代码举例

public class NoVisibility {
    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

NoVisibility可能会持续循环下去,因为读线程可能永远看不到ready的值。
另一种更奇怪的现象是,NoVisibility可能输出0,因为读线程看到了写入ready的值,但却没有看到之后写入number的值,这种现象称为“重排序”。

使用同步可以保证可见性

@NotThreadSafe
public class MutableInteger {
    private int value;

    public int get() {
        return value;
    }

    public void set(int value) {
        this.value = value;
    }
}
@ThreadSafe
public class SynchronizedInteger {
    @GuardedBy("this") 
    private int value;

    public synchronized int get() {
        return value;
    }

    public synchronized void set(int value) {
        this.value = value;
    }
}

对get()方法进行同步,保证了value的可见性。

非原子的64位操作

当线程没有同步的情况下读取变量,可能会得到一个失效的值,但是至少这个值是由之前某个线程设置的值,而不是一个随机值。这种安全性保证也被称为最低安全性。
最低安全性适合绝大多数变量,但是存在一个例外:非volatile类型的64位数值变量(double和long)。JVM允许64位的读操作或写操作分解为两个32位的操作。
因此,即使不考虑失效数据问题,在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明,或者用锁保护起来。

volatile关键字

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  • 禁止进行指令重排序。

发布与溢出

发布:使对象能够在除当前作用域之外的地方使用。
溢出:指某个对象不应该发布却被发布了。

public class PublishAndEscape {  
    //发布status  
    public static String status = "status";  
    private Object[] objects;  

    // 内部的可变状态溢出,导致外部可以直接访问并修改object  
    public Object[] getObjects() {  
        return objects;  
    }  
} 

改进代码

public class PublishAndEscape {  

    public static final String STATUS = "status";  
    private Object[] object;  

    //参见ArrayList、CopyOnWriteList
    public Object[] getObject() {  
        return Arrays.copyOf(object, object.length);  
    }   
} 

线程封闭

当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术叫做线程封闭(Thread Confinement)。

栈封闭

栈限制是线程限制的一种特例,在栈限制中,只能通过本地变量才可以触及对象。正如封装使不变约束更容易被保持,本地变量使对象更容易被限制在线程本地中。本地变量本身就被限制在执行线程中;它们存在于执行线程栈。其他线程无法访问这个栈。栈限制(也称线程内部或者线程本地用法,但是不要与核心库类的ThreadLocal混淆)

public int loadTheArk(Collection<Animal> candidates) {  
        SortedSet<Animal> animals;  
        int numPairs = 0;  
        Animal candidate = null;  

        //animals被封装在方法中,不要使它们溢出  
        animals = new TreeSet<Animal>(new SpeciesGenderComparator());  
        animals.addAll(candidates);  
        for(Animal a:animals){  
            if(candidate==null || !candidate.isPotentialMate(a)){  
                candidate = a;  
            }else{  
                ark.load(new AnimalPair(candidate,a));  
                ++numPairs;  
                candidate = null;  
            }  
        }  
        return numPairs;  
}  

ThreadLocal

这个类能使线程中的某个值与保存值的对象关联起来。
ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
当某个线程终止后,与线程对应的对象会作为垃圾回收。
ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。

public class ConnectionDispenser {
    static String DB_URL = "jdbc:mysql://localhost/mydatabase";

    private ThreadLocal<Connection> connectionHolder
            = new ThreadLocal<Connection>() {
                public Connection initialValue() {
                    try {
                        return DriverManager.getConnection(DB_URL);
                    } catch (SQLException e) {
                        throw new RuntimeException("Unable to acquire Connection, e");
                    }
                };
            };

    public Connection getConnection() {
        return connectionHolder.get();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多线程中,共享变量是指多个线程可以同时访问和操作的同一个变量共享变量的访问需要保证线程安全,以避免出现数据竞争和不确定的结果。在Java中,有多种方式可以实现多线程共享变量的操作。 1. 使用ThreadLocal类:ThreadLocal为每个线程提供了一个独立的变量副本,保证了线程之间的数据隔离。每个线程对ThreadLocal类的实例进行操作时,只会访问到自己的变量副本,而不会影响其他线程的数据。这样就实现了线程间的共享变量。 2. 使用synchronized关键字:synchronized关键字可以保证在同一时刻只有一个线程可以访问共享变量,从而避免了数据竞争的问题。通过在共享变量的读写操作前加上synchronized关键字,可以保证线程安全。 3. 使用Lock接口:Lock接口提供了显示的锁机制,通过使用Lock对象进行加锁和解锁操作,可以控制线程对共享变量的访问。与synchronized关键字相比,Lock接口提供了更灵活的锁操作,可以支持更复杂的线程同步需求。 4. 使用volatile关键字:volatile关键字用于声明共享变量,可以保证多个线程对变量的可见性,即当一个线程修改了volatile变量的值时,其他线程能够立即看到最新的值。但是volatile关键字不能保证原子性,所以在需要保证多个线程对变量的原子操作时,需要结合其他的同步机制来实现。 5. 使用并发容器:Java提供了一些并发容器,如ConcurrentHashMap、ConcurrentLinkedQueue等,这些容器可以在多线程环境下保证线程安全。通过使用这些容器,可以方便地实现多线程共享变量的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值