Java并发编程(二) - 线程(中) - 常用方法,关键字和锁

1. 线程常用方法

  • start
    这个方法让线程处于Runnable(可运行)状态。

  • yield
    使当前正在执行的线程向另一个线程交出运行权。
    yield方法属于一种启发式的方法,它会提醒调度器我愿意放弃当前的CPU资源。
    如果CPU资源不紧张,则会忽略这种提醒。

调用yield方法会使当前线程从Running状态切换到Runnable状态。

  • join
    join某个线程A,会使当前线程B进入等待,知道线程A结束生命周期,或者到达给定的时间。
    那么在此期间线程B是出于BLOCKED状态的。

 

2. 锁

在说锁之前,我们先要聊一下竞态条件(race condition)
什么是竞态条件?
竞态条件是指两个或以上线程需要共享对同意数据的存取。
该共享数据的正确性取决于线程访问数据的次序,线程之间会相互覆盖的情况。

重入锁(ReentrantLock)

有两种机制可以防止并发访问代码块。
一种是synchronized关键字,我们稍后会提到。
另一种是使用锁对象。
Java 5开始引入了ReentrantLock类
使用ReentrantLock高呼代码块的关键代码如下:

myLock.lock();
try
{
   ...
}
finally
{
   myLock.unlock();
}

eg:
Bank.java

public class Bank
{
   ...
   private var bankLock = new ReentrantLock();

   ...

   public void transfer(int from, int to, double amount) throws InterruptedException
   {
      bankLock.lock();
      try
      {
         System.out.print(Thread.currentThread());
         accounts[from] -= amount;
         System.out.printf(" %10.2f from %d to %d", amount, from, to);
         accounts[to] += amount;
         System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
      }
      finally
      {
         bankLock.unlock();
      }
   }
   
   ...
}

 

3. synchronized关键字

Java中的原子性操作
在说synchronized关键字之前,我们先来了解一下什么是原子性操作?
所谓原子性操作,是指执行一系列操作时,这些操作要么全部执行,要么全部不执行。
比如:
计数器,先读取当前值,然后+1,再更新。
这就是一个典型的读-改-写的原子性操作。

原子性操作如果不能保证,就会出现线程安全的问题。
例如下面的代码:
ThreadNotSafeCount

public class ThreadNotSafeCount {
  private Long value;

  public Long getCount() {
    return value;
  }

  public void addCount() {
    ++value;
  }
}

这个类明显是线程不安全的,因为如果有多个线程同时操作的时候,会发生竞态条件。

我们把它改成如下:
ThreadSafeCount

public class ThreadSafeCount {
  private Long value;

  public synchronized Long getCount() {
    return value;
  }

  public synchronized void addCount() {
    ++value;
  }
}

synchronized
如果一个方法声明为synchronized,那么对象的锁将保护整个方法,要调用这个方法,线程必须获得内部对象锁。
synchronized是**独占锁,没有获取锁的线程会被阻塞。

也就是说,同一时间只有一个线程可以调用这个方法。
这显然大大降低了并发性。

那么既然这样做性能会不佳,有没有更好的做法呢?
答案当然是肯定的,使用非阻塞的CAS算法实现的原子性操作类AtomicLong就是一个不错的选择!

 

4. volatile字段

Java提供了一种弱形式的同步,那就是使用volatile关键字。

如果一个字段声明为volatile,那么编译器和虚拟机就知道该字段可能被另一个线程并发更新。

确保一个变量的更新对其它线程马上可见.

当一个变量声明为volatile时,线程在写入变量时不会把值缓存在它自己的工作内存(寄存器与高速缓存)中,而是会把值刷新回主内存。因此,当其它线程读取该共享变量时,会从主内存重新获取最新值。

例子:

private volatile boolean done;
public boolean isDone() { return done; }
public void setDone() { done = true; }

上面的代码等同于下面的代码:

private boolean done;
public synchronized boolean isDone() { return done; }
public synchronized void setDone() { done = true; }

注: volatile不能完全取代synchronized同步方法,因为它缺乏原子性

 

5. CAS操作

锁在并发处理中占据了一席之地,但是锁有一个最大的弱点,那就是:
当一个线程没有获取锁的时候会被阻塞挂起。
这样就会导致线程上下文的切换和重新调度的开销。

volatile关键字的出现稍稍弥补了一部分这个弱点,但是volatile关键字虽然保证了共享变量的可见性
却不能解决类似读-改-写的这种原子性问题。

什么是CAS?
CAS即Compare and Swap。
CAS的思想很简单:三个参数,一个当前内存值V,一个旧的预期值A,一个即将更新的值B。
当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,
否则什么都不做,并返回false。

JVM中的CAS操作正是利用了提到的处理器提供的CMPXCHG指令实现的;循环CAS实现的基本思路就是循环进行CAS操作直到成功为止。

我们来看看CAS在atomic类中的应用

  public final native boolean compareAndSwapObject
     (Object obj, long valueOffset, Object expect, Object update);

  public final native boolean compareAndSwapInt
     (Object obj, long valueOffset, int expect, int update);

  public final native boolean compareAndSwapLong
    (Object obj, long valueOffset, long expect, long update);

atomic类,它们实现了对确认,更改再赋值操作的原子性。

AtomicInteger源码:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

代码解释:

  • Unsafe是CAS的核心类,Java没有方法能访问底层系统,因此需要本地方法来做,Unsafe就是一个后门,被提供来直接操作内存中的数据。

  • valueOffset:变量在内存中的偏移地址,Unsafe根据偏移地址找到获取数据。

  • value被volatile修饰,保证了内存可见性。

CAS的局限性

CAS存在ABA问题:比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。

AtomicStampedReference来解决ABA问题:这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354