栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

juc-03-synchronized、notify、notifyAll、wait、volatile

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

juc-03-synchronized、notify、notifyAll、wait、volatile

这篇文章,给大家介绍一下几个与线程间同步相关的关键字: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() {
    }

    
    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 {
    
    @Test
    public void testNoLock() throws InterruptedException {
 SyncObj so1 = new SyncObj();
 List 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;
    }

    
    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);
 }
    }

    
    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);
     }
 }
    }

    
    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);
 }
    }
}


    
    @Test
    public void testSyncObjSameLock() throws InterruptedException {
 //对象锁
 Object lock = new Object();
 SyncObj so1 = new SyncObj(lock);
 SyncObj so2 = new SyncObj(lock);
 List 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 递增不是线程安全的

    
    @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 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;

    
    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);
 }
    }

    
    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);
     }
 }
    }

    
    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);
 }
    }
}

    
    @Test
    public void testSyncClazz() throws InterruptedException {
 SyncClazz sc1 = new SyncClazz();
 SyncClazz sc2 = new SyncClazz();
 List 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。

代码:
https://github.com/wengxingxia/002juc.git

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号