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。

  • ReentrantReadWriteLock,看了些博文了解了下。线程进入读锁的前提条件,(a)没有其他线程的写锁,(b)没有写请求或者有写请求,但调用线程和持有锁的线程是同一个;线程进入写锁的前提条件,(a)没有其他线程的读锁,(b)没有其他线程的写锁。
  • ReentrantLock有两种构造,ReentrantLock(boolean fair) fair为true告诉创建的锁为公平锁,不传fair参数创建非公平锁。非公平锁是直接获取锁,没有维护等待队列;公平锁依然需要检查当前线程是否是等待队列的第一个。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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中的类,常规编程还是使用锁要更安全些,但是在涉及性能调优时,这些原子类就大有用武之地了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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++;
}
}
文章目录
  1. 1. synchronized
  2. 2. 可重入锁ReentrantLock
  3. 3. volatile
  4. 4. 原子类
  5. 5. 临界区和其他对象上同步
  6. 6. ThreadLocal