java中synchronized同步锁实现生产者消费者模式

synchronized介绍

一、基本概念

synchronized关键字是java里面用来在多线程环境下保证线程安全的同步锁;java里面有对象锁和类锁,对象锁是用在对象实例的方法上或者一个对象实例上的,而类锁是用在一个类的静态方法上或者一个类的class对象上的。所以对于对象锁,不同的实例对象的对象锁不同,但是类锁只有一个,所有的对象实例共享这个类锁

二、synchronize使用场景

1、修饰类中的普通方法:在类中的普通方法上加上synchronized修饰,锁对象是调用当前同步方法的对象实例,线程在执行该方法时,首先需要拿到该对象锁。
2、修饰类中的静态方法:在类中的静态方法上加上synchronized修饰,锁对象是当前类的Class对象,线程在执行该方法时,首先需要拿到该类锁,一个类不同的对象实例的类锁是同一把锁。
3、修饰代码块:修饰代码块时,传入的锁对象可以是对象实例也可以是类的Class对象,分别对应1和2情况,但是synchronized修饰代码块比上面两中修饰方法有一个优点,就是颗粒度更小,可以只同步我们需要同步的部分代码,其他不需要同步的代码不会被同步。

三、生产者消费者实例

1、生产者:负责不断生产票,当存储票的仓库满了的时候则停止生产票,唤醒消费者消费票

/**
	 * 生成票 .
	 */
	public void provideTickets() {

		synchronized (Tickets.class) {

			// 不断生产票
			while (true) {

				// 唤醒沉睡线程(唤醒消费者)
				Tickets.class.notify();

				try {
					//当前线程睡眠一秒钟,继续持有当前锁
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 票满了 则停止生产
				if (Tickets.tickets == Tickets.MAXNUMTICHETS) {
					System.out.println(Thread.currentThread().getName() + "--->仓库满了,不能继续生产票了");
					try {
						Tickets.class.wait();
					} catch (InterruptedException e) {
						System.out.println(Thread.currentThread().getName() + "try");
						e.printStackTrace();
					}
				}

				// 生产票 随机生产多张票,不需要一直生产到仓库满,模拟有票就可以卖
				int num = new Random().nextInt(10);
				while ((Tickets.tickets < Tickets.MAXNUMTICHETS) && (num >= 0)) {
					Tickets.tickets++;
					System.out.println(Thread.currentThread().getName() + "--->生产一张票,还有" + Tickets.tickets + "张票");
					num--;
				}

				try {
					//生产了多张票则放弃锁,让消费者消费
					Tickets.class.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}

		}

	}

2、消费者:负责不断消费票,当票被卖完了的时候唤醒生产者生产票。

/**
	 * 卖票 .
	 */
	public void saleTickets() {

		synchronized (Tickets.class) {

			// 不断买票
			while (true) {

				// 唤醒沉睡线程(唤醒生产生产票)
				Tickets.class.notify();

				try {
					//睡眠一秒钟,不释放当前锁
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				// 如果票卖完,则通知生产票
				if (Tickets.tickets == 0) {
					System.out.println(Thread.currentThread().getName() + "--->票都没了,还让我们去买,骗子");
					try {
						Tickets.class.wait();
					} catch (InterruptedException e) {
						System.out.println(Thread.currentThread().getName() + "try");
						e.printStackTrace();
					}
				}

				// 卖票 随机卖出多张票,不一定要票卖完才通知生产者
				int num = new Random().nextInt(10);
				while ((Tickets.tickets > 0) && (num > 0)) {
					Tickets.tickets--;
					System.out.println(Thread.currentThread().getName() + "--->票卖出一张,还剩" + Tickets.tickets + "张");
					num--;
				}

				try {
					//卖了多张票之后放弃锁,通知生产者生产票
					Tickets.class.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}

		}

	}

3、主程序代码:

/**
 * 票卖方 .
 * 
 * @author 小柱
 *
 */
public class Tickets {

	/**
	 * 最大票数 .
	 */
	public final static int MAXNUMTICHETS;

	/**
	 * 现有票数 .
	 */
	public static int tickets;

	static {
		MAXNUMTICHETS = 20;
		tickets = 0;
	}

	public static void main(String[] args) {
		// 票卖方
		Tickets tickets = new Tickets();
		// 生产者
		Provider provider = new Provider(tickets);
		// 消费者
		Consumer consumer = new Consumer(tickets);
		// 线程池启动生产者和消费者线程
		ExecutorService eService = Executors.newFixedThreadPool(10);
		eService.execute(provider);
		eService.execute(consumer);
	}
	/**
	 * 卖票 .
	 */
	public void saleTickets() {
			//对应上面消费票的方法
	}
	/**
	 * 生成票 .
	 */
	public void provideTickets() {
			//对应上面生产票的方法
	}
}

//-------------------------------------------------

/**
 *票卖方线程   调用 消费票的动作.
 * 
 * @author 小柱
 *
 */
class Consumer implements Runnable {

	/**
	 * 票卖方 .
	 */
	private Tickets tickes;

	public Consumer(final Tickets tickets) {
		this.tickes = tickets;
	}

	@Override
	public void run() {
		this.tickes.saleTickets();
	}

}


/**
 * 票生产方线程  调用生产票动作 .
 * 
 * @author 小柱
 *
 */
class Provider implements Runnable {

	/**
	 * 票卖方 .
	 */
	private Tickets tickes;

	public Provider(final Tickets tickets) {
		this.tickes = tickets;
	}

	@Override
	public void run() {
		this.tickes.provideTickets();
	}

}

部分运行结果:

pool-1-thread-1--->生产一张票,还有1张票
pool-1-thread-1--->生产一张票,还有2张票
pool-1-thread-1--->生产一张票,还有3张票
pool-1-thread-1--->生产一张票,还有4张票
pool-1-thread-1--->生产一张票,还有5张票
pool-1-thread-1--->生产一张票,还有6张票
pool-1-thread-1--->生产一张票,还有7张票
pool-1-thread-1--->生产一张票,还有8张票
pool-1-thread-1--->生产一张票,还有9张票
pool-1-thread-1--->生产一张票,还有10张票
pool-1-thread-1--->生产一张票,还有11张票
pool-1-thread-1--->生产一张票,还有12张票
pool-1-thread-1--->生产一张票,还有13张票
pool-1-thread-1--->生产一张票,还有14张票
pool-1-thread-1--->生产一张票,还有15张票
pool-1-thread-1--->生产一张票,还有16张票
pool-1-thread-1--->生产一张票,还有17张票
pool-1-thread-1--->生产一张票,还有18张票
pool-1-thread-2--->票卖出一张,还剩17张
pool-1-thread-2--->票卖出一张,还剩16张
pool-1-thread-2--->票卖出一张,还剩15张
pool-1-thread-1--->生产一张票,还有16张票
pool-1-thread-2--->票卖出一张,还剩15张
pool-1-thread-2--->票卖出一张,还剩14张
pool-1-thread-2--->票卖出一张,还剩13张
pool-1-thread-2--->票卖出一张,还剩12张
pool-1-thread-2--->票卖出一张,还剩11张
pool-1-thread-2--->票卖出一张,还剩10张
pool-1-thread-2--->票卖出一张,还剩9张
pool-1-thread-2--->票卖出一张,还剩8张
pool-1-thread-2--->票卖出一张,还剩7张
pool-1-thread-1--->生产一张票,还有8张票
pool-1-thread-1--->生产一张票,还有9张票
pool-1-thread-1--->生产一张票,还有10张票
pool-1-thread-1--->生产一张票,还有11张票
pool-1-thread-2--->票卖出一张,还剩10张
pool-1-thread-2--->票卖出一张,还剩9张
pool-1-thread-2--->票卖出一张,还剩8张
pool-1-thread-2--->票卖出一张,还剩7张
pool-1-thread-2--->票卖出一张,还剩6张
四、注意点

调用notify()和wait()方法必须在synchronized块里面调用;每次调用wait()方法等待的线程被唤醒之后,是接着wait()方法后面的代码继续执行,并非重新进入同步代码块;而且调用的这两个方法的对象一定是当前synchronized同步的锁对象,不然程序会报错,如下:

Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.notify(Native Method)
	at com.concurent.test.Tickets.saleTickets(Tickets.java:55)
	at com.concurent.test.Consumer.run(Tickets.java:164)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:844)

报错原因附上连接,我找到的解释报错原因解释的最好的一篇文章:

https://cloud.tencent.com/developer/article/1602598
总结

以上就是java中使用synchronized实现多线程同步安全的生产者消费者例子,注意的是,这种同步锁只能用在一个运行在一个虚拟机里面的多线程,如果是不同的虚拟机运行的多线程的同步安全需要用到分布式锁,这个稍微复杂一些。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值