synchronized关键字

当任务要执行被synchronized保护的代码片段的时候,会检查锁是否可用,然后获取锁,执行代码,释放锁。

同步规则

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

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
42
43
44
45
46
47
48
49

public class synchronizedDemo implements Runnable {
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop lock " + i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop no lock " + i);
}
}

public static void main(String[] args) {
synchronizedDemo common = new synchronizedDemo();

Thread ta = new Thread(common, "A");
Thread tb = new Thread(common, "B");
ta.start();
tb.start();
}
}
输出:
A synchronized loop lock 0
A synchronized loop lock 1
A synchronized loop lock 2
A synchronized loop lock 3
A synchronized loop lock 4
A synchronized loop no lock 0
B synchronized loop lock 0
A synchronized loop no lock 1
B synchronized loop lock 1
B synchronized loop lock 2
B synchronized loop lock 3
B synchronized loop lock 4
A synchronized loop no lock 2
A synchronized loop no lock 3
A synchronized loop no lock 4
B synchronized loop no lock 0
B synchronized loop no lock 1
B synchronized loop no lock 2
B synchronized loop no lock 3
B synchronized loop no lock 4

被synchronized修饰的代码片段会被阻塞,其它代码块不会阻塞。

使用显示的Lock对象

Lock对象必须显示的被创建、锁定和释放。优点的更加的灵活。

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
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class lockDemo implements Runnable{
Lock lock = new ReentrantLock();

@Override
public void run() {
lock.lock();
for(int i = 0; i < 10; i++) {
System.out.println("hello " + Thread.currentThread().getName());
}
lock.unlock();
}

public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
lockDemo lk = new lockDemo();
for(int i = 0;i < 5; i++) {
executorService.execute(lk);
}
}

}
  • lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
  • tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
  • tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
  • lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
  • unlock()方法,释放锁。

volatile关键字

  • 读和写一个volatile变量有全局的排序。也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。(但是并不保证经常读写volatile作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。
  • volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁

1、保证所有线程对该变量的可见性
2、禁止指令重排序优化

临界区

希望防止多个线程同时访问方法内部的部分代码而不是整个方法,分离出来的代码片段成为“临界区“,也成为同步控制块

1
2
3
synchronized(syncObject){
//临界区
}

也可以使用Lock对象实现。

线程本地存储 ThreadLocal

通常情况下,创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

  • set方法,将数据插入线程的存储对象中并返回对象
  • get方法,返回线程相关联对象的副本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class threadLocalDemo {

public static void main(String[] args) {
Thread t = new Thread() {
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();

@Override
public void run() {
super.run();
mStringThreadLocal.set("mfcheer");
System.out.println(mStringThreadLocal.get());
}
};
t.start();
}
}
输出:
mfcheer