这篇文章,给大家介绍一下几个与线程间同步相关的关键字:synchronized、notify、notifyAll、wait、volatile。
1 synchronized 内置锁
synchronized 是Java提供的一个并发控制的关键字。
synchronized 内置锁分两种:
- 对象锁:锁的是类的对象实例。作用在实例方法,或者实例代码块上。
- 类锁:锁的是
类的Class对象
,每个类的的Class对象在一个JVM中只有一个,所以类锁也只有一个。作用在 static 方法,或者 static 代码块上。
1.1 没有锁,多线程并发对SyncObj中的value值+1
public class SyncObj {
//数值
private static int value;
public SyncObj() {
}
/**
* 不加锁,SyncClazz中的类变量value +1
*/
public void addNoLock() {
for (int i = 0; i < 3; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -no lock- " + ++value);
}
}
}
public class TestSynchronized {
/**
* 测试没有锁
*
* @throws InterruptedException
*/
@Test
public void testNoLock() throws InterruptedException {
SyncObj so1 = new SyncObj();
List<Thread> list = new ArrayList<>();
// 使用多线程去执行 SyncObj.sayHello()
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 不加锁,不同线程,并发去对 so1 中的类变量value +1
// addNoLock() 没有加锁,线程不安全
so1.addNoLock();
}
});
list.add(thread);
}
for (Thread thread : list) {
thread.start();
}
TimeUnit.SECONDS.sleep(3);
}
}
运行结果,可以看到有很多重复的数值,4个重复的1,2个重复的7…:
Thread-3 -no lock- 1
Thread-4 -no lock- 1
Thread-2 -no lock- 2
Thread-1 -no lock- 1
Thread-0 -no lock- 1
Thread-3 -no lock- 2
Thread-0 -no lock- 3
Thread-1 -no lock- 4
Thread-2 -no lock- 4
Thread-4 -no lock- 5
Thread-3 -no lock- 6
Thread-4 -no lock- 7
Thread-1 -no lock- 7
Thread-0 -no lock- 8
Thread-2 -no lock- 8
1.2 对象锁
对象锁:锁的是类的对象实例。作用在实例方法,或者实例代码块上。
public synchronized void print() {
...
}
等价于,锁的都是当前的实例对象(this)。
public void print() {
synchronized (this){
...
}
}
1.2.1 对象锁,测试不同实例持有同一把对象锁
,SyncObj中的类变量 value 还是线程安全地递增
public class SyncObj {
//数值
private static int value;
//对象锁
private Object lock = null;
public SyncObj() {
}
public SyncObj(Object lock) {
this.lock = lock;
}
/**
* 对象锁,进行打印
* synchronized 作用在实例方法(未使用static修饰的方法)
* 用的锁是对象锁,是当前实例对象,也就是 this
*/
public synchronized void print() {
// 对于所有请求同一个对象锁资源的线程来说,下面的代码是线程安全的
// 其他试图访问该对象锁线程将被阻塞
for (int i = 0; i < 3; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -obj lock print- " + ++value);
}
}
/**
* 对象锁,当前实例类变量value +1
*/
public void add() {
// synchronized 作用在代码块上
// 如果 synchronized 作用的是类的对象实例,是对象锁
// 如果 synchronized 作用的是类的Class对象,是类锁
synchronized (this.lock) {
// 对于所有请求同一个对象锁资源的线程来说,下面的代码是线程安全的
// 其他试图访问该对象锁线程将被阻塞
for (int i = 0; i < 3; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -obj lock add- " + ++value);
}
}
}
/**
* 不加锁,SyncClazz中的类变量value +1
*/
public void addNoLock() {
for (int i = 0; i < 3; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -no lock- " + ++value);
}
}
}
/**
* 测试不同实例持有同一把对象锁,SyncObj中的类变量 value 还是线程安全地递增
*
* @throws InterruptedException
*/
@Test
public void testSyncObjSameLock() throws InterruptedException {
//对象锁
Object lock = new Object();
SyncObj so1 = new SyncObj(lock);
SyncObj so2 = new SyncObj(lock);
List<Thread> list = new ArrayList<>();
// 使用多线程去执行 SyncObj.sayHello()
for (int i = 0; i < 5; i++) {
//当前线程执行的实例
SyncObj instance = i < 3 ? so1 : so2;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 加对象锁,不同线程,并发去对持有同一把对象锁不同实例中的类变量value +1
// add() 加了同一把对象锁,类变量value还是线程安全递增
instance.add();
}
});
list.add(thread);
}
for (Thread thread : list) {
thread.start();
}
TimeUnit.SECONDS.sleep(3);
}
运行结果:
Thread-0 -obj lock add- 1
Thread-0 -obj lock add- 2
Thread-0 -obj lock add- 3
Thread-2 -obj lock add- 4
Thread-2 -obj lock add- 5
Thread-2 -obj lock add- 6
Thread-1 -obj lock add- 7
Thread-1 -obj lock add- 8
Thread-1 -obj lock add- 9
Thread-4 -obj lock add- 10
Thread-4 -obj lock add- 11
Thread-4 -obj lock add- 12
Thread-3 -obj lock add- 13
Thread-3 -obj lock add- 14
Thread-3 -obj lock add- 15
1.2.1 对象锁,测试不同实例持有不同对象锁
,SyncObj中的类变量 value 递增不是线程安全的
/**
* 测试不同实例持有不同对象锁,SyncObj中的类变量 value 递增不是线程安全的
*
* @throws InterruptedException
*/
@Test
public void testSyncObjDiffLock() throws InterruptedException {
//对象锁
Object lock1 = new Object();
Object lock2 = new Object();
SyncObj so1 = new SyncObj(lock1);
SyncObj so2 = new SyncObj(lock2);
List<Thread> list = new ArrayList<>();
// 使用多线程去执行 SyncObj.sayHello()
for (int i = 0; i < 5; i++) {
//当前线程执行的实例
SyncObj instance = i < 3 ? so1 : so2;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 加对象锁,不同线程,并发去对 so1 中的类变量value +1
// add() 加了同一把对象锁,类变量value还是线程安全递增
instance.add();
}
});
list.add(thread);
}
for (Thread thread : list) {
thread.start();
}
TimeUnit.SECONDS.sleep(3);
}
运行结果,可以看到有重复的数值,2个1,2个10:
Thread-0 -obj lock add- 1
Thread-3 -obj lock add- 1
Thread-0 -obj lock add- 2
Thread-3 -obj lock add- 3
Thread-3 -obj lock add- 4
Thread-0 -obj lock add- 5
Thread-4 -obj lock add- 6
Thread-2 -obj lock add- 7
Thread-4 -obj lock add- 8
Thread-2 -obj lock add- 9
Thread-2 -obj lock add- 10
Thread-4 -obj lock add- 10
Thread-1 -obj lock add- 11
Thread-1 -obj lock add- 12
Thread-1 -obj lock add- 13
1.3 类锁
类锁:锁的是
类的Class对象
,每个类的的Class对象在一个JVM中只有一个,所以类锁也只有一个。作用在 static 方法,或者 static 代码块上。
public static synchronized void print() {
...
}
等价于,锁的都是类的Class对象(SyncClazz.class)。
public static void print() {
synchronized (SyncClazz.class){
...
}
}
1.3.1 测试类锁,不同线程,不同的SyncClazz实例 并发去对SyncClazz中的类变量value +1 都是线程安全的
public class SyncClazz {
// 当前数值
private static int value;
/**
* synchronized 作用在static方法是类锁,锁的是类的Class对象
*/
public static synchronized void print() {
// 获得类锁后,下面的代码是线程安全的
for (int i = 0; i < 3; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -class lock print- " + ++value);
}
}
/**
* 类锁,SyncClazz中的类变量value +1
*/
public void add() {
// synchronized 作用在代码块上
// 如果 synchronized 作用的是类的对象实例,是对象锁
// 如果 synchronized 作用的是类的Class对象,是类锁
synchronized (SyncClazz.class) {
// 获得类锁后,下面的代码是线程安全的
for (int i = 0; i < 3; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -class lock add- " + ++value);
}
}
}
/**
* 不加锁,SyncClazz中的类变量value +1
*/
public void addNoLock() {
for (int i = 0; i < 3; i++) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -no lock- " + ++value);
}
}
}
/**
* 测试类锁,add() 加类锁,线程安全
* 不同线程,不同的SyncClazz实例 并发去对SyncClazz中的类变量value +1 都是线程安全的,value数值有序地加一递增
*
* @throws InterruptedException
*/
@Test
public void testSyncClazz() throws InterruptedException {
SyncClazz sc1 = new SyncClazz();
SyncClazz sc2 = new SyncClazz();
List<Thread> list = new ArrayList<>();
// 使用多线程去执行 SyncClazz.sayHello()
for (int i = 0; i < 5; i++) {
//当前线程执行的实例
SyncClazz instance = i < 3 ? sc1 : sc2;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 不同线程,不同的SyncClazz实例 并发去对SyncClazz中的类变量value +1
// add() 加类锁,线程安全
instance.add();
}
});
list.add(thread);
}
for (Thread thread : list) {
thread.start();
}
TimeUnit.SECONDS.sleep(3);
}
运行结果:
Thread-0 -class lock add- 1
Thread-0 -class lock add- 2
Thread-0 -class lock add- 3
Thread-4 -class lock add- 4
Thread-4 -class lock add- 5
Thread-4 -class lock add- 6
Thread-2 -class lock add- 7
Thread-2 -class lock add- 8
Thread-2 -class lock add- 9
Thread-3 -class lock add- 10
Thread-3 -class lock add- 11
Thread-3 -class lock add- 12
Thread-1 -class lock add- 13
Thread-1 -class lock add- 14
Thread-1 -class lock add- 15
2 等待和通知:notify、notifyAll、wait
notify、notifyAll、wait 这三个方法都是 Object 类中定义的方法。前提
: 持有当前对象的锁
,才能调用当前的对象的notify、notifyAll、wait 这三个方法。
- wait() 等待,当业务执行的某个条件不成立时,执行wait()方法,
放弃持有的锁
,阻塞当前的线程,等待需要的条件成立。 - notify/notifyAll 唤醒,唤醒的是所有等待当前锁的线程,这些线程重新回到就绪状态,争抢cpu资源。
2.1 notifyAll和wait,演示生产者和消费者
//生产者
public class Producer implements Runnable{
private Factory factory;
public Producer(Factory factory) {
this.factory = factory;
}
@Override
public void run() {
// 每个生产者生产3次
for (int i = 0; i < 3; i++) {
try {
// 生产时间设置 200 ms
TimeUnit.MILLISECONDS.sleep(200);
// 生产者,通过 factory 类生产
factory.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者
public class Consumer implements Runnable {
private Factory factory;
public Consumer(Factory factory) {
this.factory = factory;
}
@Override
public void run() {
// 每个消费者生产3次
for (int i = 0; i < 3; i++) {
try {
// 消费时间设置 200 ms
TimeUnit.MILLISECONDS.sleep(200);
// 消费者通过 factory 消费
factory.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//工厂
public class Factory {
// 可生产的最大数量
private final int maxCount = 5;
// 当前已有数量
private int curCount;
/**
* 生产
*/
public synchronized void produce() throws InterruptedException {
// 抢到锁
while (curCount >= maxCount) {
System.out.println("生产已满,等待消费--" + Thread.currentThread().getName());
// wait() 释放当前持有的锁
this.wait();
}
this.curCount++;
System.out.println(Thread.currentThread().getName() + "生产,当前数量--" + curCount);
// 唤醒当前正在阻塞等待this对象锁的线程
this.notifyAll();
}
/**
* 消费
*/
public synchronized void consume() throws InterruptedException {
while (curCount <= 0) {
System.out.println("无货消费,等待生产====" + Thread.currentThread().getName());
// wait() 释放当前持有的锁
this.wait();
}
this.curCount--;
System.out.println(Thread.currentThread().getName() + "消费,当前剩余数量--" + curCount);
// 唤醒当前正在阻塞等待this对象锁的线程
this.notifyAll();
}
}
// 测试生产者消费者
public class TestProducerConsumer {
@Test
public void testProducerConsumer() throws InterruptedException {
Factory factory = new Factory();
new Thread(new Producer(factory), "生产者A").start();
new Thread(new Producer(factory), "生产者B").start();
new Thread(new Producer(factory), "生产者C").start();
new Thread(new Consumer(factory), "消费者1").start();
new Thread(new Consumer(factory), "消费者2").start();
new Thread(new Consumer(factory), "消费者3").start();
TimeUnit.SECONDS.sleep(20);
}
}
运行结果:
生产者C生产,当前数量--1
生产者A生产,当前数量--2
生产者B生产,当前数量--3
消费者2消费,当前剩余数量--2
消费者3消费,当前剩余数量--1
消费者1消费,当前剩余数量--0
生产者A生产,当前数量--1
生产者B生产,当前数量--2
消费者1消费,当前剩余数量--1
生产者C生产,当前数量--2
消费者3消费,当前剩余数量--1
消费者2消费,当前剩余数量--0
无货消费,等待生产====消费者3
生产者C生产,当前数量--1
消费者1消费,当前剩余数量--0
无货消费,等待生产====消费者2
生产者B生产,当前数量--1
生产者A生产,当前数量--2
消费者2消费,当前剩余数量--1
消费者3消费,当前剩余数量--0
3 volatile
volatile: 当多个线程进行操作共享数据时,可以保证内存中的数据可见,相较于 synchronized 是一种更为轻量级的同步策略。
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的,也就是一个线程修改的结果,另一个线程马上就能看到。用volatile修饰的变量,该变量就具有可见性。
volatile修饰的变量不允许线程内部缓存和重排序,即直接读写内存,所以对其他线程是可见的。
注意:volatile只能保证所修饰内容具有可见性,但不能保证原子性。
比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。
3.1 演示变量未加volatile时,线程间可见性问题
public class TestVolatile {
public static void main(String[] args) throws InterruptedException {
FlagThread thread = new FlagThread();
thread.start();
while (!thread.isFlag()){
if(thread.isFlag()){
System.out.println("读取到 flag = true");
break;
}
}
}
}
class FlagThread extends Thread {
private boolean flag;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.flag = true;
}
}
运行结果,主线程检测不到 FlagThread 中 flag 的变化,一直在while循环中,跳不出来:
3.2 使用 volatile 修饰变量,保证变量修改后,线程间可见
public class TestVolatile {
public static void main(String[] args) throws InterruptedException {
FlagThread thread = new FlagThread();
thread.start();
while (!thread.isFlag()){
if(thread.isFlag()){
System.out.println("读取到 flag = true");
break;
}
}
}
}
class FlagThread extends Thread {
// 使用 volatile 修饰变量,保证变量修改后,线程间可见
private volatile boolean flag;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.flag = true;
}
}
运行结果:
读取到 flag = true
上面简单地演示了 synchronized
和 volatile
关键字的用法,还通过 wait() 和 notifyAll() 写了一个生产者消费者,线程间同步的Demo。
共同学习,写下你的评论
评论加载中...
作者其他优质文章