线程系列 4 - synchronized 和线程间的通信

1、关于内置锁 和 synchronized

1.1、三种内置锁对比

 
       偏向锁是在没有发生锁争用的情况下使用的,一旦有了第二个线程争用锁,偏向锁就会升级为轻量级锁。如果锁争用很激烈,轻量级锁的CAS自旋到达阈值后,轻量级锁就会升级为重量级锁。
       

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比较,仅存在纳秒级的差距。如果线程间存在锁竞争,会带来额外的锁撤销的消耗。适用只有一个线程访问的临界区场景。
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度。抢不到锁竞争的线程使用CAS自旋等待,会消耗 CPU锁占用时间很短,吞吐量低
重量级锁线程竞争不使用自旋,不会消耗 CPU线程阻塞,相应时间缓慢锁占用时间较长,吞吐量高

1.2、关于 synchronized

 

1.2.1、synchronized 的使用

  • 修饰实例方法 : 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁,即对调用该方法的对象加锁,俗称"对象锁"。
synchronized void method() {
  // todo 业务代码
}
  • 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)。
synchronized void staic method() {
  //业务代码
}
  • 修饰代码块 :指定加锁对象,对给定对象/类加锁。synchronized(this|object) 表示进入同步代码库前要获得给定对象的锁。synchronized(类.class) 表示进入同步代码前要获得 当前 class 的锁
// 线程安全的懒汉模式 - 单例
public class Singleton {
    //保证有序性,防止指令重排
    private volatile static Singleton uniqueInstance;
    private Singleton() {}
    public  static Singleton getInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
              if (uniqueInstance == null) uniqueInstance = new Singleton();
            }
        }
        return uniqueInstance;
    }
}

 

1.2.1、synchronized 的执行过程

 

  • ① 线程抢锁时,JVM首先检测内置锁对象Mark Word中的 biased_lock(偏向锁标识)是否设置成1,lock(锁标志位)是否为 01,如果都满足,确认内置锁对象为可偏向状态。

  • ② 在内置锁对象确认为可偏向状态之后,JVM检查Mark Word中 的线程ID是否为抢锁线程ID,如果是,就表示抢锁线程处于偏向锁状态,抢锁线程快速获得锁,开始执行临界区代码。

  • ③ 如果Mark Word中的线程ID并未指向抢锁线程,就通过CAS操作竞争锁。如果竞争成功,就将Mark Word中的线程ID设置为抢锁线程,偏向标志位设置为1,锁标志位设置为01,然后执行临界区代码,此时内置锁对象处于偏向锁状态。

  • ④ 如果CAS操作竞争失败,就说明发生了竞争,撤销偏向锁,进而升级为轻量级锁。

  • ⑤ JVM使用CAS将锁对象的Mark Word替换为抢锁线程的锁记录指针,如果成功,抢锁线程就获得锁。如果替换失败,就表示其他线程竞争锁,JVM尝试使用CAS自旋替换抢锁线程的锁记录指针,如果自旋成功(抢锁成功),那么锁对象依然处于轻量级锁状态。

  • ⑥如果JVM的CAS替换锁记录指针自旋失败,轻量级锁就膨胀为重量级锁,后面等待锁的线程也要进入阻塞状态。

 

2、线程间的通信

 
       线程间的通信:当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以避免无效的资源争夺。

       线程间通信的方式可以有很多种:等待-通知、共享内存、管道流。而且"等待-通知"通信方式是Java中使用普遍的线程间通信方式。该方式的线程间通信使用对象的wait()、notify()两类方法来实现。每个Java对象都有wait()、notify()两类实例方法,并且wait()、notify()方法和对象的监视器是紧密相关的。

       在调用同步对象的wait()和notify()系列方法时,“当前线程”必须拥有该对象的同步锁,也就是说,wait()和notify()系列方法需要在同步块中使用,否则JVM会抛出 IllegalMonitorStateException 异常。

 

2.1、对象的 wait() 方法

 
       对象的wait()方法的主要作用是让当前线程阻塞并等待被唤醒。wait()方法与对象监视器紧密相关,在当前线程执行 wait() 方法前,必须通过 synchronized() 方法成为对象锁的监视器的Owner。使用wait()方法时一定要放在同步块中。
 
Object类中 wait() 方法的三个版本:

  // 当前线程调用了同步对象的 wait()实例方法后,将导致当前的线程等待,
  // 当前线程进入同步对象的监视器WaitSet,等待被其他线程唤醒。
  public final void wait() throws InterruptedException {
     wait(0L);
  }

  // 当前的线程等待,等待被其他线程唤醒,或者指定的时间timeout用完,线程不再等待。
  public final native void wait(long timeoutMillis) throws InterruptedException;

  // 高精度限时等待版本,参数nanos是一个附加的纳秒级别的等待时间,而实现更加高精度的等待时间控制
  // 1秒=1000毫秒=1000,000微秒=1000,000,000纳秒。
  public final void wait(long timeoutMillis, int nanos) throws InterruptedException {……}

为方便表达,设某个同步锁对象为:locko.

对象的 wait() 方法的核心原理 大致如下:

  • ① 当线程调用了 locko(某个同步锁对象)的 wait() 方法后,JVM会将当前线程加入 locko 监视器的 WaitSet(等待集),等待被其他线程唤醒。
  • ② 当前线程会释放 locko 对象监视器的 Owner 权利,让其他线程可以抢夺 locko 对象的监视器。
  • ③ 让当前线程等待,其状态变成 WAITING。

 

2.2、对象的 notify() 方法

 
       对象的 notify() 方法的主要作用是唤醒在等待的线程。notify() 方法与对象监视器紧密相关,在执行notify()方法前,当前线程也必须通过 synchronized() 方法成为对象锁的监视器的Owner。所以调用notify()方法时也需要放在同步块中。
 
Object类中 notify() 方法的两个版本:

	// locko.notify()调用后,唤醒locko 监视器等待集中的第一条等待线程;
	// 被唤醒的线程进入EntryList,其状态从 WAITING 变成 BLOCKED。
    @HotSpotIntrinsicCandidate
    public final native void notify();
	
	
    // locko.notifyAll()被调用后,唤醒locko监视器等待集中的全部等待线程,
	// 所有被唤醒的线程进入EntryList,线程状态从 WAITING 变成 BLOCKED。
	@HotSpotIntrinsicCandidate
    public final native void notifyAll();

 

为方便表达,设某个同步锁对象为:locko.

对象的 notify() 或者 notifyAll() 方法的核心原理:

  • ① 当线程调用了locko(某个同步锁对象)的notify()方法后,JVM会唤醒locko监视器WaitSet中的第一条等待线程。

  • ② 当线程调用了locko的notifyAll()方法后,JVM会唤醒locko监视器WaitSet中的所有等待线程。

  • ③ 等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BLOCKED。

  • ④ EntryList中的线程抢夺到监视器的Owner权利之后,线程的状态从BLOCKED变成Runnable,具备重新执行的资格。

2.3、线程通信的要点

 
调用 wait() 和 notify() 系列方法进行线程通信的要点如下:

  • ① 调用某个同步对象locko的wait()和notify()类型方法前,必须要取得这个锁对象的监视锁,所以 wait() 和 notify() 类型方法必须放在 synchronized(locko) 同步块中,如果没有获得监视锁,JVM就会报 IllegalMonitorStateException 异常。

  • ② 调用wait()方法时使用while进行条件判断,如果是在某种条件下进行等待,对条件的判断就不能使用if语句做一次性判断,而是使用while循环进行反复判断。只有这样才能在线程被唤醒后继续检查 wait 的条件,并在条件没有满足的情况下继续等待。

2.4、线程通信示例

 

  • 数据缓存区 DataBuffer
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.math.BigInteger;
import java.util.LinkedList;
import java.util.List;

/**
 * 数据缓存区
 */
@Slf4j
public class DataBuffer<T> {

    /**
     * 数据缓冲区队列的最大长度
     */
    public static final int MAX_AMOUNT = 10;
    /**
     * 数据缓冲区队列当前长度
     */
    private Integer amount = 0;
    /**
     * 缓存区队列
     */
    private List<T> dataList = new LinkedList<>();

    // 同步锁对象:lockObject 用来锁队列 dataList,保证dataList线程安全
    private final Object lockObject = new Object();
    // 同步锁对象:notFull
    private final Object notFull = new Object();
    // 同步锁对象:notEmpty
    private final Object notEmpty = new Object();
    /**
     * 向数据区增加一个元素
     */
    public void add(T element) throws Exception {
        // 当缓存区队列达到最大的长度时,锁住notFull,锁住notFull对象锁等待
        while (amount >= MAX_AMOUNT) {
            synchronized (notFull) {
                log.info("缓存区队列已满!!!");
                // 等待未满通知,调用notFull对象锁的wait()方法,让当前线程进入 WAITING 状态,
                // 等待被其他线程唤醒
                notFull.wait();
            }
        }
        // 这个地方需要注意,当生产速度*生产者线程数 > 消费者速度*消费者线程数时,
        // 会出现 队列长度大于最大队列长度的情况。
        // 且 dataList 的长度最大为 11
        // add() -> dataList 缓存区队列的长度 -> 11
        synchronized (lockObject) {
            dataList.add(element);
            amount++;
            log.info("add() -> dataList 缓存区队列的长度 -> {}", dataList.size());
        }
        // 上一步 往对列中添加了元素,能走到这一步,队列中肯定不为空,
        // 所以去唤醒消费者中的 notEmpty对象锁 锁住的线程
        synchronized (notEmpty) {
            //发送未空通知
            notEmpty.notify();
        }
    }
    /**
     * 从数据区取出一条元素
     */
    public T fetch() throws Exception {
        while (amount <= 0) {
            synchronized (notEmpty) {
                log.info("缓存区队列已空!!!");
                //等待未空通知
                notEmpty.wait();
            }
        }
        T element = null;
        synchronized (lockObject) {
            // 这里需要添加校验,否则当 多线程情况下,消费速度快于生产速度时,会有异常
            if (!CollectionUtils.isEmpty(dataList)) {
                element = dataList.remove(BigInteger.ZERO.intValue());
                amount--;
                log.info("fetch() -> dataList 缓存区队列的长度 -> {}", dataList.size());
            } else {
                log.info("fetch() -> dataList 缓存区队列的长度为 0 不消费");
            }
        }
        synchronized (notFull) {
            //发送未满通知
            notFull.notify();
        }
        return element;
    }

}
  • 生产者 Producer
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description: 生产者
 */
@Slf4j
public class Producer implements Runnable {
    // 生产次数计数器
    static final AtomicInteger TURN = new AtomicInteger(1);
    // 生产的动作
    Callable action = null;
    // 生产者生产,默认耗时2s
    private int gap = 2;
    public Producer(Callable action, int gap) {
        this.action = action;
        this.gap = gap;
    }

    @Override
    public void run() {
        // 这里一直生产
        while (true) {
            try {
                //执行生产动作
                Object out = action.call();
                // 模拟生产者生产耗时
                TimeUnit.SECONDS.sleep(gap);
                //增加生产轮次
                TURN.incrementAndGet();
                //输出生产的结果
                if (null != out) {
                    log.info("线程{}->第{}次生产:{}", Thread.currentThread().getName(),
                                                             TURN.get(), out);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
  • 消费者Consumer
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class Consumer extends Thread {

    //消费总次数计数器
    static final AtomicInteger TURN = new AtomicInteger(0);
    //消费的动作
    Callable action = null;
    //消费默认消费耗时3s
    int gap = 3;
    public Consumer(Callable action, int gap) {
        this.action = action;
        this.gap = gap;
    }

    @Override
    public void run() {
        while (true) {
            //增加消费次数
            TURN.incrementAndGet();
            try {
                //执行消费动作
                Object out = action.call();
                if (null != out) {
                    log.info("线程{}->第{}次消费:{}", Thread.currentThread().getName(),
                                                               TURN.get(), out);
                }
                // 模拟消费者消费耗时
                TimeUnit.SECONDS.sleep(gap);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
  • 生产的产品 Good
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @description: 生产的产品
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Good implements Cloneable {

    private String name;

    @Override
    public Good clone() {
        Good good = null;
        try {
            good = (Good) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return good;
    }

}

  • 执行类 ThreadCommunicationDemo
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 执行类
 */
@Slf4j
public class ThreadCommunicationDemo {

    public static void main(String[] args) {
        Good goodBase = new Good("香蕉");
        final AtomicInteger goodCount = new AtomicInteger(0);
        //共享数据区,实例对象
        DataBuffer<Good> dataBuffer = new DataBuffer<>();
        //生产者执行的动作
        Callable<Good> produceAction = () -> {
            //首先生成一个随机的商品
            Good good = goodBase.clone();
            int aa = goodCount.incrementAndGet();
            log.info("生产总次数 -> {}", aa);
            good.setName(good.getName() + aa);
            //将商品加上共享数据区
            dataBuffer.add(good);
            return good;
        };
        //消费者执行的动作
        Callable<Good> consumerAction = () -> {
            // 从缓存区获取商品
            Good goods = null;
            goods = dataBuffer.fetch();
            return goods;
        };
        //线程池,用于多线程模拟测试
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        //假定共6个线程,其中有1个消费者,但是有5个生产者
        final int consumerTotal = 1;
        final int produceTotal = 5;

        for (int i = 0; i < produceTotal; i++) {
            //生产者线程每生产一个商品,需要1秒
            threadPool.submit(new Producer(produceAction, 2));
        }
        for (int i = 0; i < consumerTotal; i++) {
            //消费者线程每消费一个商品,需要两秒
            threadPool.submit(new Consumer(consumerAction, 1));
        }
        threadPool.shutdown();
    }
}
  • 执行结果:
Connected to the target VM, address: '127.0.0.1:51914', transport: 'socket'
21:52:10.320 [pool-1-thread-6] INFO com.DataBuffer - 缓存区队列已空!!!
21:52:10.320 [pool-1-thread-2] INFO com.ThreadCommunicationDemo - 生产总次数 -> 2
21:52:10.320 [pool-1-thread-3] INFO com.ThreadCommunicationDemo - 生产总次数 -> 4
21:52:10.320 [pool-1-thread-5] INFO com.ThreadCommunicationDemo - 生产总次数 -> 5
21:52:10.326 [pool-1-thread-2] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 1
21:52:10.320 [pool-1-thread-4] INFO com.ThreadCommunicationDemo - 生产总次数 -> 1
21:52:10.320 [pool-1-thread-1] INFO com.ThreadCommunicationDemo - 生产总次数 -> 3
21:52:10.327 [pool-1-thread-4] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 2
21:52:10.327 [pool-1-thread-5] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 3
21:52:10.327 [pool-1-thread-3] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 4
21:52:10.327 [pool-1-thread-1] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 5
21:52:10.330 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 4
21:52:10.331 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->1次消费:Good(name=香蕉2)
21:52:11.352 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 3
21:52:11.352 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->2次消费:Good(name=香蕉1)
21:52:12.330 [pool-1-thread-3] INFO com.Producer - 线程pool-1-thread-3->6次生产:Good(name=香蕉4)
21:52:12.330 [pool-1-thread-1] INFO com.Producer - 线程pool-1-thread-1->6次生产:Good(name=香蕉3)
21:52:12.330 [pool-1-thread-5] INFO com.Producer - 线程pool-1-thread-5->6次生产:Good(name=香蕉5)
21:52:12.330 [pool-1-thread-2] INFO com.Producer - 线程pool-1-thread-2->6次生产:Good(name=香蕉2)
21:52:12.330 [pool-1-thread-1] INFO com.ThreadCommunicationDemo - 生产总次数 -> 7
21:52:12.330 [pool-1-thread-4] INFO com.Producer - 线程pool-1-thread-4->6次生产:Good(name=香蕉1)
21:52:12.330 [pool-1-thread-5] INFO com.ThreadCommunicationDemo - 生产总次数 -> 8
21:52:12.330 [pool-1-thread-3] INFO com.ThreadCommunicationDemo - 生产总次数 -> 6
21:52:12.330 [pool-1-thread-2] INFO com.ThreadCommunicationDemo - 生产总次数 -> 9
21:52:12.330 [pool-1-thread-1] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 4
21:52:12.330 [pool-1-thread-4] INFO com.ThreadCommunicationDemo - 生产总次数 -> 10
21:52:12.331 [pool-1-thread-5] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 5
21:52:12.331 [pool-1-thread-4] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 6
21:52:12.331 [pool-1-thread-3] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 7
21:52:12.332 [pool-1-thread-2] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 8
21:52:12.369 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 7
21:52:12.369 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->3次消费:Good(name=香蕉5)
21:52:13.389 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 6
21:52:13.389 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->4次消费:Good(name=香蕉4)
21:52:14.336 [pool-1-thread-3] INFO com.Producer - 线程pool-1-thread-3->8次生产:Good(name=香蕉6)
21:52:14.336 [pool-1-thread-2] INFO com.Producer - 线程pool-1-thread-2->8次生产:Good(name=香蕉9)
21:52:14.336 [pool-1-thread-4] INFO com.Producer - 线程pool-1-thread-4->11次生产:Good(name=香蕉10)
21:52:14.336 [pool-1-thread-5] INFO com.Producer - 线程pool-1-thread-5->11次生产:Good(name=香蕉8)
21:52:14.336 [pool-1-thread-1] INFO com.Producer - 线程pool-1-thread-1->11次生产:Good(name=香蕉7)
21:52:14.336 [pool-1-thread-2] INFO com.ThreadCommunicationDemo - 生产总次数 -> 12
21:52:14.336 [pool-1-thread-3] INFO com.ThreadCommunicationDemo - 生产总次数 -> 11
21:52:14.336 [pool-1-thread-5] INFO com.ThreadCommunicationDemo - 生产总次数 -> 14
21:52:14.336 [pool-1-thread-4] INFO com.ThreadCommunicationDemo - 生产总次数 -> 13
21:52:14.336 [pool-1-thread-2] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 7
21:52:14.336 [pool-1-thread-1] INFO com.ThreadCommunicationDemo - 生产总次数 -> 15
21:52:14.336 [pool-1-thread-3] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 8
21:52:14.337 [pool-1-thread-1] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 9
21:52:14.337 [pool-1-thread-4] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 10
21:52:14.337 [pool-1-thread-5] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 11
21:52:14.403 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 10
21:52:14.403 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->5次消费:Good(name=香蕉3)
21:52:15.419 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 9
21:52:15.419 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->6次消费:Good(name=香蕉7)
21:52:16.342 [pool-1-thread-5] INFO com.Producer - 线程pool-1-thread-5->16次生产:Good(name=香蕉14)
21:52:16.342 [pool-1-thread-3] INFO com.Producer - 线程pool-1-thread-3->16次生产:Good(name=香蕉11)
21:52:16.342 [pool-1-thread-1] INFO com.Producer - 线程pool-1-thread-1->16次生产:Good(name=香蕉15)
21:52:16.342 [pool-1-thread-5] INFO com.ThreadCommunicationDemo - 生产总次数 -> 16
21:52:16.342 [pool-1-thread-3] INFO com.ThreadCommunicationDemo - 生产总次数 -> 17
21:52:16.342 [pool-1-thread-2] INFO com.Producer - 线程pool-1-thread-2->16次生产:Good(name=香蕉12)
21:52:16.342 [pool-1-thread-4] INFO com.Producer - 线程pool-1-thread-4->16次生产:Good(name=香蕉13)
21:52:16.343 [pool-1-thread-5] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 10
21:52:16.343 [pool-1-thread-2] INFO com.ThreadCommunicationDemo - 生产总次数 -> 19
21:52:16.343 [pool-1-thread-1] INFO com.ThreadCommunicationDemo - 生产总次数 -> 18
21:52:16.343 [pool-1-thread-4] INFO com.ThreadCommunicationDemo - 生产总次数 -> 20
21:52:16.343 [pool-1-thread-3] INFO com.DataBuffer - 缓存区队列已满!!!
21:52:16.343 [pool-1-thread-4] INFO com.DataBuffer - 缓存区队列已满!!!
21:52:16.343 [pool-1-thread-2] INFO com.DataBuffer - 缓存区队列已满!!!
21:52:16.343 [pool-1-thread-1] INFO com.DataBuffer - 缓存区队列已满!!!
21:52:16.443 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 9
21:52:16.443 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->7次消费:Good(name=香蕉8)
21:52:16.443 [pool-1-thread-3] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 10
21:52:17.459 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 9
21:52:17.460 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->8次消费:Good(name=香蕉10)
21:52:17.460 [pool-1-thread-4] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 10
21:52:18.357 [pool-1-thread-5] INFO com.Producer - 线程pool-1-thread-5->17次生产:Good(name=香蕉16)
21:52:18.358 [pool-1-thread-5] INFO com.ThreadCommunicationDemo - 生产总次数 -> 21
21:52:18.358 [pool-1-thread-5] INFO com.DataBuffer - 缓存区队列已满!!!
21:52:18.453 [pool-1-thread-3] INFO com.Producer - 线程pool-1-thread-3->18次生产:Good(name=香蕉17)
21:52:18.453 [pool-1-thread-3] INFO com.ThreadCommunicationDemo - 生产总次数 -> 22
21:52:18.453 [pool-1-thread-3] INFO com.DataBuffer - 缓存区队列已满!!!
21:52:18.468 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 9
21:52:18.468 [pool-1-thread-2] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 10
21:52:18.468 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->9次消费:Good(name=香蕉6)
21:52:19.462 [pool-1-thread-4] INFO com.Producer - 线程pool-1-thread-4->19次生产:Good(name=香蕉20)
21:52:19.462 [pool-1-thread-4] INFO com.ThreadCommunicationDemo - 生产总次数 -> 23
21:52:19.462 [pool-1-thread-4] INFO com.DataBuffer - 缓存区队列已满!!!
21:52:19.477 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 9
21:52:19.477 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->10次消费:Good(name=香蕉9)
21:52:19.477 [pool-1-thread-1] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 10
21:52:20.484 [pool-1-thread-6] INFO com.DataBuffer - fetch() -> dataList 缓存区队列的长度 -> 9
21:52:20.484 [pool-1-thread-2] INFO com.Producer - 线程pool-1-thread-2->20次生产:Good(name=香蕉19)
21:52:20.484 [pool-1-thread-5] INFO com.DataBuffer - add() -> dataList 缓存区队列的长度 -> 10
21:52:20.484 [pool-1-thread-2] INFO com.ThreadCommunicationDemo - 生产总次数 -> 24
21:52:20.484 [pool-1-thread-6] INFO com.Consumer - 线程pool-1-thread-6->11次消费:Good(name=香蕉12)
21:52:20.484 [pool-1-thread-2] INFO com.DataBuffer - 缓存区队列已满!!!
…………
…………
…………

 

3、synchronized 八锁现象

3.1、Lock1

 

import java.util.concurrent.TimeUnit;

/**
 * 该例子 call() 和 sendMes() 共用一个锁,谁先拿到谁先执行
 */
public class Lock1 {
    public static void main(String[] args) {
        Phone1 phone = new Phone1();
        new Thread(() -> phone.sendMes(), "A").start();
        // sleep 一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone.call(), "B").start();
    }
}
class Phone1 {
    // synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
    public synchronized void sendMes() {
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }
    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }
}
/************************* 执行结果: *****************************/
A -> 发送消息
B -> 打电话

 

3.2、Lock2

 

import java.util.concurrent.TimeUnit;

/**
 * 该例子 call() 和 sendMes() 共用一个锁,谁先拿到谁先执行
 */
public class Lock2 {
    public static void main(String[] args) {
        Phone2 phone2 = new Phone2();
        new Thread(() -> phone2.sendMes(), "A").start();
        // sleep 一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone2.call(), "B").start();
    }
}

class Phone2 {
    // synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
    public synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4); // sleep 4秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }
    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }
}

/************************* 执行结果: *****************************/

A -> 发送消息
B -> 打电话

 

3.3、Lock3

 

import java.util.concurrent.TimeUnit;

/**
 * call() 方法没有 synchronized 不受锁影响
 */
public class Lock3 {
    public static void main(String[] args) {
        Phone3 phone3 = new Phone3();
        new Thread(() -> phone3.sendMes(), "A").start();
        // sleep 一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone3.call(), "B").start();
    }
}

class Phone3 {
    // synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
    public synchronized void sendMes() {
        // sleep 4秒
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }
    // 该方法没有 synchronized 不受锁影响
    public void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }

}

/************************* 执行结果: *****************************/
B -> 打电话
A -> 发送消息

 

3.4、Lock4

 

import java.util.concurrent.TimeUnit;

/**
 * 两个对象,两个非静态同步方法,该例子实际上是两把锁。
 */
public class Lock4 {
    public static void main(String[] args) {
        Phone4 phone41 = new Phone4();
        Phone4 phone42 = new Phone4();
        new Thread(() -> phone41.sendMes(), "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone42.call(), "B").start();
    }
}

class Phone4 {
    // synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
    public synchronized void sendMes() {
        // sleep 4秒
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }
    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }
}
/************************* 执行结果: *****************************/
B -> 打电话
A -> 发送消息

 

3.5、Lock5

 

import java.util.concurrent.TimeUnit;

/**
 * 一个对象,两个静态同步方法,该例子实际上是共用一个锁(Phone5.class),谁先拿到谁先执行。
 */
public class Lock5 {
    public static void main(String[] args) {
        Phone5 phone5 = new Phone5();
        new Thread(() -> phone5.sendMes(), "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone5.call(), "B").start();
    }
}

class Phone5 {
    // synchronized 修饰静态方法,是对调用该方法的class 模板加锁,俗称"类锁"。
    public static synchronized void sendMes() {
        // sleep 4秒
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }
    public static synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }
}
/************************* 执行结果: *****************************/
A -> 发送消息
B -> 打电话

 

3.6、Lock6

 

import java.util.concurrent.TimeUnit;

/**
 * 两个对象,两个静态同步方法,该例子实际上是共用一个锁(Phone6.class),谁先拿到谁先执行。
 */
public class Lock6 {
    public static void main(String[] args) {
        Phone6 phone61 = new Phone6();
        Phone6 phone62 = new Phone6();
        new Thread(() -> phone61.sendMes(), "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone62.call(), "B").start();
    }
}

class Phone6 {
    // synchronized 修饰静态方法,是对调用该方法的class 模板加锁,俗称"类锁"。
    public static synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }
    public static synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }
}
/************************* 执行结果: *****************************/
A -> 发送消息
B -> 打电话

 

3.7、Lock7

 

import java.util.concurrent.TimeUnit;

/**
 * 一个对象,一个静态同步方法,一个非静态同步方法
 * 该例子实际上是两个锁(Phone7.class 和 phone7对象)。
 * 说明:如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,
 * 而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,
 * 因为访问静态 synchronized 方法占用的锁是当前类的锁,
 * 而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
 */
public class Lock7 {
    public static void main(String[] args) {
        Phone7 phone7 = new Phone7();
        new Thread(() -> phone7.sendMes(), "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone7.call(), "B").start();
    }
}

class Phone7 {
    // synchronized 修饰静态方法,是对调用该方法的class 模板加锁,俗称"类锁"。
    public static synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }
    // synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }
}
/************************* 执行结果: *****************************/
B -> 打电话
A -> 发送消息

 

3.8、Lock8

 

import java.util.concurrent.TimeUnit;

/**
 * 两个对象,一个静态同步方法,一个非静态同步方法
 * 该例子实际上是两个锁(Phone8.class 和 phone82对象)。
 */
public class Lock8 {

    public static void main(String[] args) {

        Phone8 phone81 = new Phone8();
        Phone8 phone82 = new Phone8();
        new Thread(() -> phone81.sendMes(), "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> phone82.call(), "B").start();
    }

}

class Phone8 {

    // synchronized 修饰静态方法,是对调用该方法的class 模板加锁,俗称"类锁"。
    public static synchronized void sendMes() {
        // sleep 4秒
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " -> 发送消息");
    }

    // synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " -> 打电话");
    }

}
/************************* 执行结果: *****************************/
B -> 打电话
A -> 发送消息

 

 
 
 
线程系列博文:
 
线程系列 1 - 线程基础
线程系列 2 - 并发编程之线程池 ThreadPool 的那些事
线程系列 3 - 关于 CompletableFuture
线程系列 5 - CAS 和 JUC原子类
线程系列 6 - JUC相关的显示锁
线程系列 7 - JUC高并发容器类
 
 
 
 
 
.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值