`
awenhaowenchao
  • 浏览: 71974 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

<Java Concurrency in Practice> Part I: Fundamentals原理 Chapter 2. Thread Safety

阅读更多
--注:由于Java Concurrency in Practice时间久远业已脱销,所以本人对读到的一些要点进行整理,主要用来个人进一步深化学习,书中原文会加中英文注释,自己的白话仅有中文。

Chapter 2. Thread Safety线程安全

If multiple threads access the same mutable state variable without appropriate synchronization, your program is broken. There are three ways to fix it:
    如果多个线程在没有适当的同步机制情况下访问同一个状态易变的变量, 那么你的程序容易被打破。下面有三种解决方式:

Don't share the state variable across threads;
不要在多个线程间共享状态变量;

Make the state variable immutable; or
保持状态变量不可变;

Use synchronization whenever accessing the state variable.
当并发访问状态变量时候注意使用同步机制(同步代码块或锁);

2.1. What is Thread Safety? 什么是线程安全

    A class is thread-safe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code.
  
   一个类如果被多个线程访问且运行正常那么他是安全的,无论被运行环境如何安排或交错那些线程执行,并且没有在被调用代码块添加任何其他同步机制或其他协调方式。

2.1.1. Example: A Stateless Servlet

@ThreadSafe
public class StatelessFactorizer implements Servlet {
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        encodeIntoResponse(resp, factors);
    }
}


以上例子符合保持线程安全的三种方式。

2.2. Atomicity原子性

Listing 2.2. Servlet that Counts Requests without the Necessary Synchronization. Don't Do this.

@NotThreadSafe
public class UnsafeCountingFactorizer implements Servlet {
    private long count = 0;

    public long getCount() { return count; }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;//非原子操作
        encodeIntoResponse(resp, factors);
    }

}


  以上程序是对count进行计算,需要对其进行read+modify+write的操作,在多个线程并发访问程序的时候,如果某个线程在read+modify+write三步操作的任何一步阻塞,则可能会造成数据的不同步,进而影响数据真实性。那么,这里管read+modify+write三步操作为不可拆分的操作,只有read+modify+write三步操作同时成功执行,才能保证数据真实性,那么三个操作构成了原子操作
2.2.1. Race Conditions竞争状况

    有竞争才会涉及并发访问。

2.2.2. Example: Race Conditions in Lazy Initialization

Listing 2.3. Race Condition in Lazy Initialization. Don't Do this.

@NotThreadSafe
public class LazyInitRace {
    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (instance == null)//多个线程访问时候,如果某个线程阻塞,那么这个线程重新抢占时间后它读到的数据可能就是脏数据了
            instance = new ExpensiveObject();
        return instance;
    }
}


2.2.3. Compound Actions复合行为(操作)
   
    上面说了read+modify+write为一个不可拆分的原子操作,而其实质上还是三个操作,将这三个操作在程序级加上同步机制使其构成一个原子操作,我们指出这三个操作就是一个复合操作。那么在java的api也提供了一些原子操作工具类,来替代显示的synchronize这些标识。
@ThreadSafe
public class CountingFactorizer implements Servlet {
    private final AtomicLong count = new AtomicLong(0);//可以用原子方式更新的 long 值。

    public long getCount() { return count.get(); }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        count.incrementAndGet();
        encodeIntoResponse(resp, factors);
    }
}




上面图片中列出了软件包 java.util.concurrent.atomic(类的小工具包,支持在单个变量上解除锁定的线程安全编程。 ) 中的支持同步更新的线程安全类。

2.3. Locking 锁

2.3.1. Intrinsic Locks 内在锁

synchronized (lock) {
    // Access or modify shared state guarded by lock
}

2.3.2. Reentrancy 重入
 
    当一个锁被某个线程持有,那么其他线程不可再获取这把锁,但是持有这把锁的线程可以重新持有。其实在运行时环境中有一个对同步锁计数的机制,即锁空闲态时候,count=0,当某个线程持有这把锁后count++,并记录这把锁的owner,当持有这把锁的线程再次持有这把锁后继续count++。

Listing 2.7. Code that would Deadlock if Intrinsic Locks were Not Reentrant.

public class Widget {
    public synchronized void doSomething() {
        ...
    }
}

public class LoggingWidget extends Widget {
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling doSomething");
        //比如程序运行时,某个线程执行到这里阻塞的话,没有重新进入super.doSomething(),那么即造成死锁。
        super.doSomething();
    }
}


2.4. Guarding State with Locks 用锁保持状态

    如何正确使用锁机制,要根据实际情况有一个合适的力度,不能随随便的就把某个方法直接synchronized(可能将不需要同步的部分也加锁,那么对性能造成了一定的影响),也不能将read-modify-write的某一个或者两个操作放入原子操作(没有正确分析原子操作构成)。

2.5. Liveness and Performance 活性与性能

Listing 2.8. Servlet that Caches its Last Request and Result.

@ThreadSafe
public class CachedFactorizer implements Servlet {
    @GuardedBy("this") private BigInteger lastNumber;
    @GuardedBy("this") private BigInteger[] lastFactors;
    @GuardedBy("this") private long hits;
    @GuardedBy("this") private long cacheHits;

    public synchronized long getHits() { return hits; }
    public synchronized double getCacheHitRatio() {
        return (double) cacheHits / (double) hits;
    }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        synchronized (this) {
            ++hits;
            if (i.equals(lastNumber)) {
                ++cacheHits;
                factors = lastFactors.clone();
            }
        }
        if (factors == null) {
            factors = factor(i);
            synchronized (this)  {
                lastNumber = i;
                lastFactors = factors.clone();
            }
        }
        encodeIntoResponse(resp, factors);
    }
}


以上例子,正式遵循保证原子操作,又不滥用原子操作,保证程序的数据可靠与性能良好。

  • 大小: 27.8 KB
0
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics