实现一个容器,提供两个方法,add,size
写两个线程,线程1添加10个元素到容器中, 线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
这是一道线程的面试题,看起来挺简单的,做起来实际上有不少小坑,是一道很有意思的面试题,目前我能想到的是大概七种解法,下面把我的解法分享给大家。你们有不一样的解法欢迎到评论区留言
解法一:传统的wait and notify实现
public class WithoutVolatile {
//创建一个线程安全的ArrayList集合,其实在这个解法中不用这个线程安全类也是可以的,这样加上反而会影响效率,因为wait and notify是在synchronized 基础上才可以实现的
//因为synchronized 本身就是保证线程安全的锁
// private List<Integer> list=new ArrayList ();
private List<Integer> list= Collections.synchronizedList(new ArrayList <> ());
public void add(int i){
list.add (i);
}
public int getSize(){
return list.size ();
}
public static void main(String[] args) {
//创建容器类
WithoutVolatile withoutVolatile = new WithoutVolatile ( );
Thread thread = new Thread (() -> {
//锁住共有的容器类
synchronized (withoutVolatile){
for (int i = 0; i < 10; i++) {
try {
//每次检测一下容器是否等于5
if (withoutVolatile.getSize ()==5){
//等于5,让改线程进入等待状态
withoutVolatile.wait ();
}
withoutVolatile.add (i);
System.out.println ("添加:"+i);
TimeUnit.SECONDS.sleep (1);
//每次执行完添加唤醒在等待队列的线程
//notify 只有唤醒功能并没有释放锁的功能
withoutVolatile.notify ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
}
});
Thread thread1 = new Thread (() -> {
synchronized (withoutVolatile){
try {
//检测一下容器是否等5
if (withoutVolatile.getSize ()!=5){
//不等于五,就让该线程进入等待
withoutVolatile.wait ();
}
System.out.println ("监听到了,结束。。。。");
//监听后,唤醒在等待的线程继续往下执行
withoutVolatile.notify ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
});
thread1.start ();
thread.start ();
}
}
解法一看起来比较繁乱,notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行整个通信过程比较繁琐。
我门能不能尝试用join的方式呢,不用线程通信的形式?
解法二 join+synchronized/ReentrantLock的方式
public class WithoutVolatile5 {
private volatile List list= Collections.synchronizedList (new LinkedList<> ());
static Thread t2=null;
static Thread t1=null;
public void add(Object i){
list.add (i);
}
public int size(){
return list.size ();
}
public static void main(String[] args) {
WithoutVolatile5 w=new WithoutVolatile5 ();
t1 = new Thread (() -> {
try {
synchronized (w){
for (int i = 0; i < 5; i++) {
w.add (i);
System.out.println("添加 " + i);
}
}
//在for循环添加到5时让改线程释放锁然后启动t2线程,通过调用join方法来保证t2线程执行时不被打断,保证了线程的安全性
t2.start ();
t2.join ();
synchronized (w){
for (int i = 5; i < 10; i++) {
w.add (i);
System.out.println("添加 " + i);
}
}
} catch (InterruptedException e) {
e.printStackTrace ( );
}
});
t2 = new Thread (() -> {
System.out.println ("监听到了,结束。。。。");
});
t1.start ();
}
}
这种解法虽然没有wait跟notify繁琐,但是加了两次锁,会导致效率的降低。synchronized跟ReentrantLock是一样写法,我在这就不写了,(注意用ReentrantLock一定要解锁!!!)因为synchronized是jvm自动解锁,但还是ReentrantLock没有。所以一定要解锁。
接下来的解法都是JUC并发包下的类来实现的
解法三 用门闩(CountDownLatch)的方式实现
public class WithoutVolatile1 {
private volatile List list=Collections.synchronizedList (new LinkedList <> ());
public void add(Object i){
list.add (i);
}
public int size(){
return list.size ();
}
public static void main(String[] args) {
WithoutVolatile1 w=new WithoutVolatile1();
//为什么要创建两个门闩呢?
//假如你用一个CountDownLatch 的话会导致线程二在执行的同时出现了CPU的上下文切换,导致数据的不一致,可以多试几次就会发现问题
CountDownLatch countDownLatch = new CountDownLatch (1);
CountDownLatch latch = new CountDownLatch (1);
Thread thread = new Thread (() -> {
try {
for (int i = 0; i < 10; i++) {
if (w.size ( ) == 5) {
countDownLatch.countDown ( );
latch.await ();
}
w.add (i);
System.out.println("添加 " + i);
}
} catch (InterruptedException e) {
e.printStackTrace ( );
}
});
Thread thread1 = new Thread (() -> {
try {
if(w.size ()!=5){
countDownLatch.await ( );
}
System.out.println ("监听到了,结束。。。。");
latch.countDown ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
});
thread.start ();
thread1.start ();
}
}
解法四 ReentrantLock中的Condition方式
做法跟wait跟notify一样的只是参数不一而已
public class WithoutVolatile6 {
private volatile List list=Collections.synchronizedList (new LinkedList <> ());
public void add(Object i){
list.add (i);
}
public int size(){
return list.size ();
}
public static void main(String[] args) {
WithoutVolatile6 w=new WithoutVolatile6 ();
ReentrantLock lock=new ReentrantLock ();
//用condition必须要结合lock锁,跟synchronized搭配wait与notify是一个原则
Condition condition = lock.newCondition ( );
Thread thread = new Thread (() -> {
try {
lock.lock ();
for (int i = 0; i < 10; i++) {
if (w.size ( ) == 5) {
condition.await ();
}
w.add (i);
System.out.println("添加 " + i);
condition.signal ();
}
} catch (InterruptedException e) {
e.printStackTrace ( );
}finally {
lock.unlock ();
}
});
Thread thread1 = new Thread (() -> {
try {
lock.lock ();
if(w.size ()!=5){
condition.await ();
}
System.out.println ("监听到了,结束。。。。");
condition.signal ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}finally {
lock.unlock ();
}
});
thread.start ();
thread1.start ();
}
}
解法五 用LockSupport阻塞的方式实现
public class WithoutVolatile2 {
private volatile List list= Collections.synchronizedList (new LinkedList<> ());
//为什么将线程声明在此?
//是因为LockSupport.park是静态方法,需要传参的话就必须是静态的,那为什么要声明在外部呢,这是因为lambada内要声明final,所以直接定义在外部了
static Thread t2=null;
static Thread t1=null;
public void add(Object i){
list.add (i);
}
public int size(){
return list.size ();
}
public static void main(String[] args) {
WithoutVolatile2 w=new WithoutVolatile2();
t2 = new Thread (() -> {
if(w.size ()!=5){
//指定让那个线程阻塞
LockSupport.park (t2);
}
System.out.println ("监听到了,结束。。。。");
LockSupport.unpark (t1);
});
t1 = new Thread (() -> {
for (int i = 0; i < 10; i++) {
if (w.size () == 5) {
LockSupport.unpark (t2);
LockSupport.park (t1);
}
w.add (i);
System.out.println ("添加:" + i);
}
});
t1.start ();
t2.start ();
}
}
解法六 用信号量(Semaphore)的方式实现
public class WithoutVolatile3 {
private volatile List list= Collections.synchronizedList (new LinkedList<> ());
static Thread t2=null;
static Thread t1=null;
public void add(Object i){
list.add (i);
}
public int size(){
return list.size ();
}
public static void main(String[] args) {
WithoutVolatile3 w=new WithoutVolatile3();
Semaphore semaphore=new Semaphore (1);
t1=new Thread (()->{
try {
for (int i = 0; i < 5; i++) {
//通过信号量的方式来保证这个区间是线程安全的
semaphore.acquire ();
w.add (i);
System.out.println ("添加:" + i);
semaphore.release ();
}
t2.start ();
t2.join ();
for (int i = 5; i < 10; i++) {
semaphore.acquire ();
w.add (i);
System.out.println ("添加:" + i);
semaphore.release ();
}
} catch (InterruptedException e) {
e.printStackTrace ( );
}
});
t2 = new Thread (() -> {
try {
semaphore.acquire ( );
System.out.println ("监听到了,结束。。。。");
semaphore.release ( );
} catch (InterruptedException e) {
e.printStackTrace ( );
}
});
t1.start ();
}
}
这种解法跟join是一样的,没有线程之间的互调,所以不是很合理
解法七 用栅栏(Cyclicbarrier)的方式实现
public class WithoutVolatile4 {
private volatile List list= Collections.synchronizedList (new LinkedList<> ());
static Thread t2=null;
static Thread t1=null;
public void add(Object i){
list.add (i);
}
public int size(){
return list.size ();
}
public static void main(String[] args) {
WithoutVolatile4 w=new WithoutVolatile4();
CyclicBarrier cyclicBarrier=new CyclicBarrier (1);
t1 = new Thread (() -> {
try {
for (int i = 0; i < 5; i++) {
w.add (i);
System.out.println("添加 " + i);
cyclicBarrier.await ();
}
t2.start ();
t2.join ();
for (int i = 5; i < 10; i++) {
w.add (i);
System.out.println("添加 " + i);
cyclicBarrier.await ();
}
} catch (InterruptedException e) {
e.printStackTrace ( );
}catch (BrokenBarrierException e) {
e.printStackTrace ( );
}
});
t2 = new Thread (() -> {
System.out.println ("监听到了,结束。。。。");
});
t1.start ();
}
}
我个人觉得用栅栏也可以实现门闩的做法,只是我感觉声明两个栅栏有点浪费,所以就用了上面这种做法
以上就是我所发现的解法!有写的不对的或者有新的解法欢迎评论区指正