今天在做一道作业题(附代码和输出结果):
使用生产者和消费者模式实现,交替输出: 假设只有两个线程,输出以下结果: t1-->1 t2-->2 t1-->3 t2-->4 要求:必须交替,并且t1线程负责输出奇数。t2线程负责输出偶数。 两个线程共享一个数字,每个线程执行时都要对这个数字进行:++
这道作业题看似简单,但却有一个坑,我们都知道synchronized拿到对象的锁,再使用完释放锁后,其他线程如果要获取这个对象锁,这个对象的内存地址一定不能够改变,不然就找不到了,最后大家都陷入到了无限的等待。
然而对于Java中的Interge对象有一个特殊的地方,就是在jvm的方法区中有整形常量池,而且有自动拆箱和装箱的操作,拆分操作:先将nums对象拆箱转化为基本数据类型的整形,然后执行nums++,最后再重新赋值给一个新的整形对象,所以当一个线程执行完这个操作:
synchronized (nums){ if (nums%2==0){ try { nums.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"----->num:"+nums++); nums.notify(); }
这个时候你所拿到的Interge对象在自增后内存地址已经发生了改变,所以你在用这个新对象唤醒其他线程时,会出现IllegalMonitorStateException异常,提示你不是这个对象的拥有者,不能够唤醒在这个对象上活动的线程。
解决方法:在使用整形数据自增并不会改变锁对象的内存地址---》使用整形数组,数组的对象是在堆内存中的,只要线程共享这个数组对象,对数组内部数据的操作并不会引起数组内存地址的改变,所以也就不会出现该问题。
package com.cooler.javase.ThreadHomework; import java.util.ArrayList; import java.util.List; /* * 1、使用生产者和消费者模式实现,交替输出: 假设只有两个线程,输出以下结果: t1-->1 t2-->2 t1-->3 t2-->4 要求:必须交替,并且t1线程负责输出奇数。t2线程负责输出偶数。 两个线程共享一个数字,每个线程执行时都要对这个数字进行:++ [注]用Interge对象不可以,涉及到整形常量池的地址问题,自增会导致对象的内存地址发生改变,最后都找不到锁机制 * */ public class ThreadTest { public static void main(String[] args) { Integer[] integers = {1}; //共享了这个数字 Thread myThreadOdd = new Thread(new MyThreadOdd(integers)); Thread myThreadEven = new Thread(new MyThreadEven(integers)); myThreadOdd.setName("myThreadOdd"); myThreadEven.setName("myThreadEven"); myThreadOdd.start(); myThreadEven.start(); } } class MyThreadOdd implements Runnable{ private Integer[] nums; public MyThreadOdd(Integer[] nums) { this.nums = nums; } @Override public void run() { while (true){ synchronized (nums){ if (nums[0]%2==0){ try { nums.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"----->num:"+nums[0]++); nums.notify(); } } } } class MyThreadEven implements Runnable{ private Integer[] nums; public MyThreadEven(Integer[] nums) { this.nums = nums; } @Override public void run() { while (true){ synchronized (nums){ if (nums[0]%2==1){ try { nums.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"----->num:"+nums[0]++); nums.notify(); } } } }
输出结果:myThreadOdd----->num:1
myThreadEven----->num:2
myThreadOdd----->num:3
myThreadEven----->num:4
myThreadOdd----->num:5
myThreadEven----->num:6
myThreadOdd----->num:7
myThreadEven----->num:8
myThreadOdd----->num:9
myThreadEven----->num:10
myThreadOdd----->num:11
myThreadEven----->num:12
myThreadOdd----->num:13
myThreadEven----->num:14
myThreadOdd----->num:15
myThreadEven----->num:16
myThreadOdd----->num:17