Java多线程 线程同步

如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你需要使用同步,并且,读写线程都必须用相同的监视器锁同步。--Brain同步规则

 

synchronized

  1. 所有对象都自动含有单一的锁,当在调用一个对象的任意synchronized方法时,此对象将被加锁。
  2. 对于某个特定对象来说,所有的synchronized方法共享同一个锁。所以某个线程在访问对象的一个synchronized方法时,其他线程访问该对象的任何synchronized方法都将被阻塞。
  3. 一个任务可以多次获得对象锁,如在调用对象的一个方法时,该方法又调用了其他的方法。JVM负责跟踪对象被加锁的次数,这也是锁的可重入性,不可重入的锁可能造成死锁。
  4. 每个类也有一个锁,每个类都是一个class对象,synchronized static方法可以防止对static数据的并发访问
  5. 在使用并发时,最好将字段设置为private,防止线程直接访问字段。

可重入锁ReentrantLock

在java.util.concurrent.locks包中定义了显式的Lock,该Lock锁需要显式的创建、锁定和释放。比起synchronized关键字,显式的Lock锁使用起来更繁琐,在使用时也更有可能出错,但有更强大的功能。locks包中包含了两种锁ReentrantLock和ReentrantReadWriteLock。

  1. ReentrantReadWriteLock,看了些博文了解了下。线程进入读锁的前提条件,(a)没有其他线程的写锁,(b)没有写请求或者有写请求,但调用线程和持有锁的线程是同一个;线程进入写锁的前提条件,(a)没有其他线程的读锁,(b)没有其他线程的写锁。
  2. ReentrantLock有两种构造,ReentrantLock(boolean fair) fair为true告诉创建的锁为公平锁,不传fair参数创建非公平锁。非公平锁是直接获取锁,没有维护等待队列;公平锁依然需要检查当前线程是否是等待队列的第一个。
public class LockTest {
    private int current = 0;
    private Lock lock = new ReentrantLock();
    
    public int next(){
        //lock.lock();
        boolean flag = lock.tryLock();
        if(flag){
            try{
                ++current;
                Thread.yield();
                ++current;
                return current;
            }finally{
                lock.unlock();
            }
        }else{
            System.out.println("locked");
            return -1;
        }
    }
}

显示锁可以去尝试获取锁,这是比synchronized关键字更为灵活的地方。tryLock(long timeout, TimeUnit unit)还可以设置你等待获取锁的时间。由于Lock锁更有可能出错,除非解决特殊问题,否则使用synchronized关键字即可。

volatile

使用volatile关键字可以使你定义的基本类型变量的赋值和返回操作为原子操作。

volatile可以是声明的字段具有可视性。可视性指只要你对字段进行了写操作,那么所有的读操作就都可以看到这个修改。因为线程对某一字段的修改,可能不会直接体现在内存中,其他线程也就获取不到最新的值。

volatile关键字应该慎重使用,需要进行线程同步时,首选还是synchronized关键字。

原子类

Java SE5提供了AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类。这些类以boolean compareAndSet(int expect, int update)的形式进行原子性条件的更新。这些原子类都有基本的get、set操作,用来获取和设置值。

Atomic类的设计主要用来构建java.util.concurrent中的类,常规编程还是使用锁要更安全些,但是在涉及性能调优时,这些原子类就大有用武之地了。

public class AtomicityTest implements Runnable {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new Timer().schedule(new TimerTask() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.err.println("aborting");
                System.exit(0);
            }
        }, 5000);
        ExecutorService exec = Executors.newCachedThreadPool();
        AtomicityTest at = new AtomicityTest();
        exec.execute(at);
        
        while(true){
            int val = at.getValue();
            if(val % 2 != 0){
                System.out.println(val);
                System.exit(0);
            }
        }
    }
    
    private AtomicInteger i = new AtomicInteger(0);
    public int getValue(){
        return i.get();
    }
    
    private void evenIncrement(){
        i.addAndGet(2);
        System.out.println(i);
    }
    
    @Override
    public void run(){
        while(true)
            evenIncrement();
    }
}

临界区和其他对象上同步

有时我们只希望某部分代码不被多个线程同时访问,这类被同步的代码块叫临界区。临界区可以使用synchronized关键字来建立。

synchronized(syncObject) {   //临界区代码   }

某个线程在进入临界区代码块时,必须获得syncObject对象上的锁。需要注意的是,syncObject既可以是对象本身this,也可以使用其他的对象。在介绍synchronized关键字时说过,所有对象都含有一个锁,临界区就是通过对象锁来实现的。这样多个线程可以同时进入同一个对象,只要该对象被访问的代码块使用了不同的对象锁。

ThreadLocal

ThreadLocal通过根除对变量的共享来防止多线程中共享资源的冲突。ThreadLocal为使用相同的变量的每个不同的线程都创建不同存储。ThreadLocal对象通常当做静态字段来存储。下面的代码,相当于每一个线程都为其设置了一个Integer id。

public class ThreadLocalTest {
    public static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
        protected synchronized Integer initialValue() {
            return 100;
        }
    };
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new Timer().schedule(new TimerTask() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.exit(0);
            }
        }, 5000);
        
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i=0; i<10; ++i){
            exec.execute(new Accesor(value.get()));
        }
        exec.shutdown();
    }

}
class Accesor implements Runnable {
    private int id;
    
    public Accesor(Integer id){
        this.id = id.intValue();
        System.out.println("init value: " + this.id);
    }
    
    public void run(){
        while(true) id++;
    }
}
posted @ 2015-06-14 21:28  Spanda  阅读(220)  评论(0编辑  收藏  举报