线程之间的通信二
要求用线程顺序打印AaBbCc…Zz
这道题一开始想到的就是sync加wait跟notify解法。这也是最经典的解法,同时也是这道题最想要的答案,但是但我们学了神奇的JUC并发包之后,解法就变得多种多样了,以下是我目前能想到的解法,分享给大家!
解法一:sync加wait跟notify
public class T01_00_sync_wait_notify {
//定义两个数组,一个小写字母,一个大写字母
static char[] lowers=new char[26];
static char[] uppers=new char[26];
static {
//提前初始化数组的值
for(int i = 1;i<=26;i++){
//将26个字母存入数组
lowers[i-1]=(char)(96+i);
//默认是小写用char包装类转换成大写的
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
//定义一个唯一的对象作为锁
final Object o=new Object ();
new Thread (()->{
synchronized (o){
//我是先优先启动了打印大写字母的线程,所以开头是大写的
//要是强制要求它永远是第一个打印大写的加个布尔值在线程2进行判断
//这个布尔值是要保证可见性的加volatile关键字来保证
for (char a:uppers) {
System.out.print(a);
try {
//唤醒等待中的线程,notify 并不会释放锁
o.notify ();
//将线程一等待,同时释放锁
o.wait ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
//这里为啥要用notify ?
//是为了防止线程一跟线程二其中还有线程在等待中的情况
o.notify ();
}
},"t1").start ();
new Thread (()->{
synchronized (o){
for (char a:lowers) {
System.out.print (a);
o.notify ();
try {
Thread.sleep (1);
o.wait ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
o.notify ();
}
},"t2").start ();
}
}
解法二:sync加wait跟notify
强制让那个线程先输出,加一个布尔值来控制
public class T02_00_sync_wait_notify {
static char[] lowers=new char[26];
static char[] uppers=new char[26];
//增加了一个布尔值来作为一个打印的标准
static volatile boolean state=false;
static {
for(int i = 1;i<=26;i++){
lowers[i-1]=(char)(96+i);
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
final Object o=new Object ();
new Thread (()->{
synchronized (o){
for (char a:uppers) {
System.out.print(a);
//这里是优先让线程一打印大写字母
//在这里将state设置为true之后线程二马上能知道,这就是volatile保证线程可见性的功能
state=true;
try {
o.notify ();
o.wait ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
o.notify ();
}
},"t1").start ();
new Thread (()->{
synchronized (o){
//判断state状态是否为false如果是则让线程二进入等待状态
while (!state){
try {
o.wait ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
for (char a:lowers) {
System.out.print (a);
o.notify ();
try {
Thread.sleep (1);
o.wait ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
o.notify ();
}
},"t2").start ();
}
}
解法三:Lock锁跟Condition
public class LockConditionTest {
static char[] lowers=new char[26];
static char[] uppers=new char[26];
static {
for(int i = 1;i<=26;i++){
lowers[i-1]=(char)(96+i);
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
//创建lock锁
Lock lock=new ReentrantLock ();
//这里为啥要创建二个condition呢,这样可以精准指定执行那个线程
Condition upperCondition = lock.newCondition ( );
Condition lowerCondition = lock.newCondition ( );
new Thread (()->{
try {
//用lock锁必须要手动释放锁,他没有sync那样可以自动释放锁
lock.lock ();
for (char c:uppers) {
System.out.print(c);
//唤醒小写线程锁
lowerCondition.signal ();
//让大写线程等待
upperCondition.await ();
}
//循环结束后怕等待队列还有为唤醒的线程
lowerCondition.signal ();
} catch (Exception e) {
e.printStackTrace ( );
} finally {
lock.unlock ();
}
},"t1").start ();
new Thread (()->{
try {
lock.lock ();
for (char c:lowers) {
System.out.print(c);
upperCondition.signal ();
lowerCondition.await ();
}
upperCondition.signal ();
} catch (Exception e) {
e.printStackTrace ( );
} finally {
lock.unlock ();
}
},"t2").start ();
}
}
解法四:LockSupport的park与unpark
public class LockSupportTest {
//为啥将线程定义在这里,为啥不定在main方法中,这是因为我用了lamda表达式的原因,lamda的匿名内部类实现不能用可变的外部变量,只能用final关键字修饰
static Thread t1=null,t2=null;
static char[] lowers=new char[26];
static char[] uppers=new char[26];
static volatile boolean state=false;
static {
for(int i = 1;i<=26;i++){
lowers[i-1]=(char)(96+i);
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
t1=new Thread (()->{
for (char b:uppers) {
System.out.print(b);
//叫醒线程二
LockSupport.unpark (t2);
//park线程一
LockSupport.park ();
}
});
t2=new Thread (()->{
for (char a:lowers) {
//进来先阻塞住
//之后t1唤醒t2直接执行下面的打印跟唤醒t1线程
LockSupport.park ();
System.out.print(a);
LockSupport.unpark (t1);
}
});
t1.start ();
t2.start ();
}
}
解法五:用CAS的解法
首先定义两个唯一的变量,自己定义一个死循环,来检测变量是否变了,变了就打印,打印完就换另一个变量
public class CASTest {
//这里为啥用枚举呢,因为枚举是不可变的,可以保证唯一性
enum ThreadEnum{T1,T2}
//加了volatile 保证线程之间的可见性
static volatile ThreadEnum threadEnum=ThreadEnum.T1;
static char[] lowers=new char[26];
static char[] uppers=new char[26];
static {
for(int i = 1;i<=26;i++){
lowers[i-1]=(char)(96+i);
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
new Thread (()->{
for (char c:uppers) {
//假如ThreadEnum不是T1就让它无限循环直到时间片用完。或者cpu调度了其他线程
while (threadEnum!=ThreadEnum.T1){}
//如果是则打印
System.out.print(c);
//将枚举变量改变为T2的
threadEnum=ThreadEnum.T2;
}
}).start ();
new Thread (()->{
for (char c:lowers) {
while (threadEnum!=ThreadEnum.T2){ }
System.out.print(c);
threadEnum=ThreadEnum.T1;
}
}).start ();
}
}
解法六:AtomicXXX类实现
理论上来说AtomicXXX类里的所有类型都是可以实现的,我这里只是举利了一种AtomicInteger来说明
其实解法跟前面的CAS是差不多的,也是通过原子性操作与死循环来保证线程的之间的通信
用CAS的解法会消耗CPU性能,过多的死循环,因为循环CPU不会释放执行权,还是该线程在执行
public class AtomicIntegerTest {
static char[] lowers=new char[26];
static char[] uppers=new char[26];
static {
for(int i = 1;i<=26;i++){
lowers[i-1]=(char)(96+i);
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
//创建AtomicInteger 类,给默认值为1
AtomicInteger aint = new AtomicInteger (1);
new Thread (()->{
for (char c : uppers) {
//判断线程是否为1
//不是就无限循环等待
while (aint.get ()!=1){}
//是则修改以下操作
System.out.print (c);
aint.set (2);
}
}).start ();
new Thread (()->{
for (char c : lowers) {
while (aint.get ()!=2){ }
System.out.print (c);
aint.set (1);
}
}).start ();
}
}
解法七 BlockingQueue用对列的方式
public class BlockingQueueTest {
static char[] lowers=new char[26];
static char[] uppers=new char[26];
static {
for(int i = 1;i<=26;i++){
lowers[i-1]=(char)(96+i);
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
//创建两个队列,思路跟之前的condition差不多精准唤醒线程
BlockingQueue upperQueue=new ArrayBlockingQueue (1);
BlockingQueue lowerQueue=new ArrayBlockingQueue (1);
new Thread (()->{
for (char c:uppers) {
//打印
System.out.print (c);
//给小写队列存值
lowerQueue.offer (1);
try {
//给大写队列取值,队列没值的情况下就会阻塞
upperQueue.take ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
},"t1").start ();
new Thread (()->{
for (char c:lowers) {
try {
lowerQueue.take ();
System.out.print (c);
upperQueue.offer (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
},"t1").start ();
}
}
解法八 TransferQueue的方式
public class TransferQueueTest {
static char[] lowers=new char[26];
static char[] uppers=new char[26];
static {
for(int i = 1;i<=26;i++){
lowers[i-1]=(char)(96+i);
uppers[i-1]=Character.toUpperCase ((char)(96+i));
}
}
public static void main(String[] args) {
//创建队列
TransferQueue<Character> transferQueue = new LinkedTransferQueue ();
new Thread (()->{
for (char c:uppers){
try {
//将值存入队列,会直接阻塞,等待其他线程取值
transferQueue.transfer (c);
System.out.print(transferQueue.take ());
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
}).start ();
new Thread (()->{
for (char c:lowers){
try {
//将值取出来,之后
System.out.print(transferQueue.take ());
//将值存入并阻塞
transferQueue.transfer (c);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
}).start ();
}
}