2.Java线程同步机制

1.锁

Java平台中的锁:

  • 内部锁:synchronized关键字实现
  • 显式锁:java.concurrent.locks.Lock接口的实现类实现的

2.synchronized关键字

synchronized关键字所引导的代码块(同步块)就是临界区。锁句柄是一个对象的引用。

synchronized(锁句柄){
//在此代码块中访问共享数据
}

同步实例方法相当于以"this"为引导锁的同步块,即以下两种方法本质是一样的:

public synchronized short nextSequence(){
	//在此代码块中访问共享数据
}
public short nextSequence(){
	synchronized (this){
		//在此代码块中访问共享数据
	}
}

作为锁句柄的变量通常采用private final修饰,如:private final Object lock = new Object()
作为所句柄的变量也可以是一个类对象,如下所示:

public class SyncExample{
	public static void staticMethod(){
		synchronized (SyncExample.class){
		//在此代码块中访问共享数据
		}
	}
}

//相当于
public class SyncExample{
	public static synchronized void staticMethod(){
		//在此代码块中访问共享数据
	}
}

3.Lock接口

显式锁是java.concurrent.locks.Lock接口的实例。java.util.concurrent.locks.ReentrantLock是Lock接口的默认实现类。
使用显式锁的示例代码:

public class LockbasedCircularSeqGenerator implements CircularSeqGenerator {
    private final Lock lock = new ReentrantLock();
    private short sequence = -1;

    @Override
    public short nextSequence() {
        lock.lock();
        try {
            if (sequence >= 999) {
                sequence = 0;
            } else {
                sequence++;
            }
            return sequence;
        } finally {
            lock.unlock();
        }
    }
}

ReentrantLock既支持非公平锁也支持公平锁,正如一个构造器所示:

 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平锁保障锁调度的公平性往往是以增加了线程的暂停和唤醒的可能性,即增加了上下文切换为代价的。总的老说使用公平锁的开销比使用非公平锁的开销要大,因此显式锁默认使用的是非公平调度策略。

4. 读写锁

读写锁允许多个线程可以同时读取共享变量,但是一次只允许一个线程对共享变量进行更新。任何线程读取共享变量的时候,其他线程无法更新这些变量;一个线程更新共享变量的时候,其他任何线程都无法访问该变量。
读写锁使用示例代码:

public class ReadWriteLockUsage {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();

    // 读线程执行该方法
    public void reader() {
        readLock.lock(); // 申请读锁
        try {
            // 在此区域读取共享变量
        } finally {
            readLock.unlock();// 总是在finally块中释放锁,以免锁泄漏
        }
    }

    // 写线程执行该方法
    public void writer() {
        writeLock.lock(); // 申请读锁
        try {
            // 在此区域访问(读、写)共享变量
        } finally {
            writeLock.unlock();// 总是在finally块中释放锁,以免锁泄漏
        }
    }
}

读写锁适合的场景:

  • 只读操作比写操作频繁得多;
  • 读线程持有锁的时间比较长。

5.volatile关键字

volatile关键字常被称为轻量级锁,其作用与锁的作用有相同的地方:保证可见性和有序性。所不同的是,在原子性方面它仅能保障写volatile变量操作的原子性,但没有锁的排他性;其次,volatile关键字的使用不会引起上下文切换。

volatile关键字仅保障对被修饰的变量的读操作、写操作本身的原子性。如果要保障对volatile变量的赋值操作的原子性,那么这个赋值操作不能涉及任何共享变量的访问。

6. CAS和原子变量

public class CASBasedCounter {
    /**
     * 这里使用AtomicLongFieldUpdater只是为了便于讲解和运行该实例,
     * 实际上更多的情况是我们不使用AtomicLongFieldUpdater,而是使用
     * java.util.concurrent.atomic包下的其他更为直接的类。
     */
    private final AtomicLongFieldUpdater<CASBasedCounter> fieldUpdater;
    private volatile long count;

    public CASBasedCounter() throws SecurityException, NoSuchFieldException {
        fieldUpdater = AtomicLongFieldUpdater.newUpdater(CASBasedCounter.class,
                "count");
    }

    public static void main(String[] args) throws Exception {
        final CASBasedCounter counter = new CASBasedCounter();
        Thread t;
        Set<Thread> threads = new HashSet<Thread>();
        for (int i = 0; i < 20; i++) {
            t = new Thread(new Runnable() {
                @Override
                public void run() {
                    Tools.randomPause(50);
                    counter.increment();
                }
            });
            threads.add(t);
        }

        for (int i = 0; i < 8; i++) {
            t = new Thread(new Runnable() {
                @Override
                public void run() {
                    Tools.randomPause(50);
                    Debug.info(String.valueOf(counter.vaule()));
                }
            });
            threads.add(t);
        }

        // 启动并等待指定的线程结束
        Tools.startAndWaitTerminated(threads);
        Debug.info("final count:" + String.valueOf(counter.vaule()));
    }

    public long vaule() {
        return count;
    }

    public void increment() {
        long oldValue;
        long newValue;
        do {
            oldValue = count;// 读取共享变量当前值
            newValue = oldValue + 1;// 计算共享变量的新值
        } while (/* 调用CAS来更新共享变量的值 */!compareAndSwap(oldValue, newValue));
    }

    /*
     * 该方法是个实例方法,且共享变量count是当前类的实例变量,因此这里我们没有必要在方法参数中声明一个表示共享变量的参数
     */
    private boolean compareAndSwap(long oldValue, long newValue) {
        boolean isOK = fieldUpdater.compareAndSet(this, oldValue, newValue);
        return isOK;
    }
}
  • 保障操作的原子性
  • 不能保障可见性
  • 不会导致上下文切换
  • ABA问题及其规避
    如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 "ABA"问题。
    ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。JDK 1.5 以后的 AtomicStampedReference 类就是用来解决 ABA 问题的,其中的 compareAndSet() 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

7.static关键字

  • 保障线程初次读取静态变量的可见性
  • 保障静态变量被发布前是初始化完毕的

8.final关键字

  • 保障被修饰的对象在其被发布前是初始化完毕的
  • 不能保障包含final字段的对象本身的可见性
  • 不会导致上下文切换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值