前言:
面试题:
关于线程同步的面试题,凡是从时间角度或者是优先级角度考虑解决思路的,基本全不对!凡是从join sleep考虑的,99.99%的不对,线程优雅的结束,一般不用interrupt stop resume。
经典面试题:synchronized和ReentrantLock的区别是什么?
答案:ReentrantLock可以多个队列,synchronized只有一个队列,ReentrantLock可以做公平锁,synchronized只有非公平(没有公平可言),ReentrantLock还可以tryLock尝试上锁,上不了锁可以去做别的(做出一定的处理),但是synchronized只能上来就死等傻傻等待,等不到就死在那,第四个区别:锁可以被打断,就是我在锁的过程中我可以让别人打断我,打折我的腿我就醒了这个叫lock.incorruptibly可以做这个操作就是entry.lock,但是synchronized不可以,因为synchronized不能打断,除非把整个线程给砍掉。
提示:以下是本篇文章正文内容,下面案例可供参考
示例:
用两个线程,一个输出字母,一个输出数字,交替输出
1A2B3C4D5E...26Z
案例:wait();notify(); 写法 ,不推荐使用!(示例):
package com.aaa.blbl;
public class T06_sync_wait_notify {
public static void main(String[] args) {
//把o当中一把锁来使用,让数字和字母交替执行
final Object o = new Object();
//构建两个数组用来输出
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(() -> {
synchronized (o)
{
for (char c : aI)
{
System.out.println(c);
try {
o.notify();
o.wait();//让出锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();//必须,否则无法终止程序
}
},"t1").start();
new Thread(() -> {
synchronized (o)
{
for (char c : aC)
{
try {
System.out.println(c);
o.notify();
o.wait();//让出锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();//必须,否则无法终止程序
}
},"t2").start();
}
}
解析:
O=锁,wait():进入这把锁锁所关联的那个等待队列。
举例:A上厕所,厕所里面有人怎么办,A到旁边一个有队列的地方去排队,
排排坐吃果果等什么时候轮到A了,A再去上厕所。
所以此锁附带的有一个等待队列,但是这个等待队列是所有的关于这把锁的线程都可以
让自己进去,不管他是进来上厕所还是洗手或者是接热水等等等等其它的各种骚操作,都可以进去,那先叫醒上洗手的或者是接热水的上厕所的不叫醒能不能做到。
答案:notify()做不到,notify的意思是说让线程调度器去选一个线程出来,他选谁这事你管不了,也就是说如果t1和t2两个线程全进去了t3想叫醒其中的某一个是叫不醒的,只能去按照特定算法去叫醒其中的一个,但是叫醒哪一个程序员无法控制,当然出了notify之外还有另外一种叫notifyall,notifyall原理:叫醒等待队列里面的所有线程,叫醒之后去抢那把锁,谁抢到算谁的,但是还是没有达到需求,因为会叫醒那些不想叫醒的线程。
某种条件下某种叫醒某些人他就是个队列。
举例2:我想叫醒某某些叫醒某些特定人群,该怎么办,我没办法,做不到。
举例3:著名的生产者消费者问题,生产者存放东西,消费者取东西 。
然后:必须对锁同步因为生产满了之后,生产者需要去进行休息等待进入队列。
消费者:消费者消费空了都得进入队列等待,那生产者生产满了,我想叫醒特定的那个消费者线程,里边那些生产者不想叫醒其余的消费者怎么办?
经典案例Condition:推荐使用
代码如下(示例):
package com.aaa.blbl;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T09_lock_condition {
public static void main(String[] args) {
//构建两个数组用来输出
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
Lock lock = new ReentrantLock();
//一个Condition代表一个队列,有几个Condition代表有几个队列
Condition collectionT1 = lock.newCondition();//队列1
Condition collectionT2 = lock.newCondition();//队列2
CountDownLatch latch = new CountDownLatch(1);
//进入线程:可选:让哪个队列进入当前线程
new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
for (char c : aI) {
System.out.println(c);
collectionT2.signal();
collectionT1.await();
}
collectionT2.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
},"t1").start();
new Thread(() ->{
lock.lock();
try {
for (char c : aC)
{
System.out.println(c);
latch.countDown();
collectionT1.signal();
collectionT2.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
},"t2").start();
}
}
好玩案例:酌情使用,使用同步队列实现
代码如下(示例):
package com.aaa.blbl;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;
/*
TransferQueue最大好处容量为0,TransferQueue相当于一个交换场所
*/
public class T11_TransFerQueue {
public static void main(String[] args) {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
TransferQueue<Character> queue = new LinkedTransferQueue<>();
new Thread(() ->{
try {
for (char c : aI)
{
//queue.take()从里边拿一个字符出来就把该字符打印出来
System.out.println(queue.take());
//queue.transfer(c) 往里递字符进去
queue.transfer(c);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() ->{
try {
for (char c : aC)
{
//线程二是先递一个字符,再去拿一个打印出来。
//注意:transfer、take都是阻塞的,就是说如果take不到程序不会往下执行
//如果transfer过去了但是没有人从我手里拿走,那么我的程序也不会往下执行,类似于交换场所。
queue.transfer(c);
System.out.println(queue.take());
}
}catch (Exception e)
{
e.printStackTrace();
}
},"t2").start();
}
}
总结
例如:以上就是今天要讲的内容,本文仅仅简单介绍了两种方式来实现效果,而如果只是利用线程实现交替输出效果还是有很多方式的!当然,笔者深度还欠缺,如果错误还请指正。