手撕奇偶线程交替打印1-100,3个窗口售票(防超卖)。!!!大厂最爱手撕java并发编程题

先来个单例模式压压惊,单例模式也是面试常考的哦。

饿汉式(jvm保证线程安全)

/*
饿汉式由jvm来保证一个类的<cintit>方法在多线程环境下被正确地加锁同步,如果多个线程同时去初始化一个类,
那么只会有其中一个线程去执行这个类的<cinit>方法,其他线程都需要阻塞等待,直到活动线程执行完毕。
如果一个类的<cinit>方法执行耗时太长,可能造成多个线程阻塞等待。
 */
public class SingtonHungry {
	public SingtonHungry() {
	}
	private static SingtonHungry instance=new SingtonHungry();
	public static SingtonHungry getInstance(){
		return instance;
	}
}

懒汉式1(非改进版,存在线程安全问题)

/*
如果abc三个线程同时调用静态的getInstance(),
会造成多个线程获得不同的instance实例(分配的内存地址不同),
这就有违单例模式了,所以这是有问题的。
 */
public class SingletonLazy {
	private static SingletonLazy instance=null;
	private static SingletonLazy getInstance(){
		if(instance==null){
			instance=new SingletonLazy();
		}
		return instance;
	}
}

懒汉式2(改进版,线程安全,但吞吐量太低)

/*
使用synchronized锁来保护临界区共享资源
 */
public class SingletonLazy {
	private static SingletonLazy instance=null;
	private static SingletonLazy getInstance(){
		synchronized (SingletonLazy.class) {
				/*这里是线程安全的,因为每次进来都需要加锁。
				打个比方,ab线程同时进来,a线程进入monitorEnter的逻辑,b线程进入entryList阻塞队列去等待锁释放,
				a线程初始化完instance后,进入monitorExit逻辑,释放锁。b线程还需要再次判断instance是否为null。
				 */
			if(instance==null) {
				instance = new SingletonLazy();
			}
		}
		//缺点就是,不管instance是否初始化完成,后续的每个线程进来获取instance实例都需要加锁,效率太低。
		return instance;
	}
}

懒汉式3(双重检查锁,存在线程安全问题,因为现代cpu多级指令流水线提高吞吐量的同时,可能会导致指令重排序,需要大量压测才能测试出来这个问题)

/*
双重检查锁
 */
public class SingletonLazy {
	public SingletonLazy() {
	}
	private static SingletonLazy instance=null;
	private static SingletonLazy getInstance(){
		/*
		这样看似已经做到线程安全了,并且后续线程进来只需判断instance是否为null,发现不为null直接返回即可。
		但还是存在问题的实际上。
		 */
		/*
		分析:第一重检查的instance==null的逻辑判断是没有加锁的,并且也没有使用volatile关键字来修饰。所以极端情况:
		a、b两个线程同时进入第一重检查,都判断instance==null,而a线程加锁成功;b阻塞等待。
		a线程进入第二重检查发现instance==null,所以会执行instance==new SingletonLazy()的逻辑。
		这个语句有两个逻辑,(1)内存分配;(2)执行init(),就是对象的构造方法;(3)静态变量的指针指向堆中的对象
		所以这种情况可能会导致指令重排序,将堆中的对象的地址先赋值给静态变量,再调用init();
		如果此时有c线程进来,拿到的就是一个不完整的instance实例对象。
		 */
		if(instance==null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}

懒汉式4(双重检查锁,线程安全,volatile的内存屏障来保证可见性和有序性)

/*
双重检查锁+volatile关键字
 */
public class SingletonLazy {
	public SingletonLazy() {
	}
	private static volatile SingletonLazy instance=null;
	private static SingletonLazy getInstance(){
		/*
		volatile关键字能保证共享变量在多线程之间的可见性与有序性。原理是内存屏障:
		storeStore屏障保证其之前的所有的读写指令执行完,不与volatile域写重排序;
		storeLoad屏障保证volatile域写后将高速缓存中的共享变量强制刷新到内存中,不能等到高速缓存满再刷脏。
		volatile域读会在前后施加loadLoad和loadStore两个屏障,
		loadLoad屏障保证volatile域读共享变量一定到内存中读取,让自己高速缓存的共享变量值无效;
		loadStore屏障保证volatile域读后下面所有的读写操作不与它重排序
		 */
		if(instance==null) {
			synchronized (SingletonLazy.class) {
				if (instance == null) {
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}

奇偶线程交替打印1-100。(为啥奇线程的while条件判断是<100,看方法三的前注释)

方法一:锁机制(这个看习惯,我喜欢用ReentrantLock)

/*
odd线程打印奇数、even线程打印偶数
方法一:使用锁机制
 */
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class oneLock {
	private static ReentrantLock lock=new ReentrantLock();
	private static int count=1;
	private static Condition oddCondition;
	private static Condition evenCondition;
	public static void main(String[] args) throws InterruptedException {
		oddCondition=lock.newCondition();
		evenCondition=lock.newCondition();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count<100){
					lock.lock();
					System.out.println(Thread.currentThread().getName()+"打印了"+count++);
					evenCondition.signalAll();
					try {
						oddCondition.await();
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						lock.unlock();
					}
				}
                //这儿退出while循环后需要让奇线程获取锁,让偶线程别wait了,要不然程序一直无法退出,
                //奇线程先拿到锁后再唤醒偶线程,最后释放锁
				if(count>100) {
					lock.lock();
					evenCondition.signalAll();
					lock.unlock();
				}
			}
		},"奇线程").start();
        //main线程在调用.start()启动后,先睡10毫秒,让奇线程一定先于偶线程执行;
		Thread.sleep(10);
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count<=100){
					lock.lock();
					System.out.println(Thread.currentThread().getName()+"打印了"+count++);
					oddCondition.signalAll();
					try {
						evenCondition.await();
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						lock.unlock();
					}
				}
			}
		},"偶线程").start();
	}
}

方法二:volatile关键字修饰flag标志位+原子整数(cas)

//cpu空转就完事了啊
import java.util.concurrent.atomic.AtomicInteger;
public class twoVolatile {
	private static volatile boolean flag=true;
	private static AtomicInteger count=new AtomicInteger(1);
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count.get()<100){
					if(flag){
						System.out.println(Thread.currentThread().getName()+"打印了"+count.getAndIncrement());
						flag=false;
					}
				}
			}
		},"奇线程").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count.get()<=100){
					if(!flag){
						System.out.println(Thread.currentThread().getName()+"打印了"+count.getAndIncrement());
						flag=true;
					}
				}
			}
		},"偶线程").start();
	}
}

方法三:Semaphore,这个不仅可以用于线程间通信还可以用于同一主机的进程间通信

/*
odd线程打印奇数、even线程打印偶数
方法三:使用信号量
因为最后一个打印的数是偶数,所以奇线程阻塞等待的时候acquire(),
因为他在99的时候判断了是小于等于100的,然后再++成100,然后再进入循环发现还是小于等于100.
所以才能进入acuire()中,所以如果奇线程的方法执行的while(count<=100)它会打印101。
 */
public class threeSemaphore {
	private static Semaphore oddSemaphore=new Semaphore(1);
	private static Semaphore evenSemaphore=new Semaphore(0);
	private static int count=1;
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count<100) {
					try {
						oddSemaphore.acquire();
						System.out.println(Thread.currentThread().getName()+"打印了"+count++);
						evenSemaphore.release();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}, "奇线程").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count<=100) {
					try {
						evenSemaphore.acquire();
						System.out.println(Thread.currentThread().getName()+"打印了"+count++);
						oddSemaphore.release();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}, "偶线程").start();
	}
}

三个窗口卖100张票,防止超卖问题

方法一:锁机制

import java.util.concurrent.locks.ReentrantLock;
/*
这里三个线程的代码都是一样的,或者直接用Runnable来创建任务即可,然后创建三个线程一起执行这个任务。
每个线程睡上10毫秒,是为了防止一个窗口把票卖完
*/
public class oneLock {
	private static int count=100;
	private static ReentrantLock lock=new ReentrantLock();
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count>=1) {
					try {
						Thread.sleep(10);
						lock.lock();
						if (count >= 1) {
							System.out.println(Thread.currentThread().getName() + "售出了第" + count-- + "张票");
						} else {
							break;
						}
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						lock.unlock();
					}
				}
			}
		},"窗口二").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count>=1) {
					try {
						Thread.sleep(10);
						lock.lock();
						if (count >= 1) {
							System.out.println(Thread.currentThread().getName() + "售出了第" + count-- + "张票");
						} else {
							break;
						}
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						lock.unlock();
					}
				}
			}
		},"窗口三").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count>=1) {
					try {
						Thread.sleep(10);
						lock.lock();
						if (count >= 1) {
							System.out.println(Thread.currentThread().getName() + "售出了第" + count-- + "张票");
						} else {
							break;
						}
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						lock.unlock();
					}
				}
			}
		},"窗口一").start();
	}
}

方法二:原子整数cas

import java.util.concurrent.atomic.AtomicInteger;
public class caseTwo {
	private static AtomicInteger count=new AtomicInteger(100);
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count.get()>=1){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"售出了"+count.getAndDecrement()+"张票");
				}
			}
		}, "窗口一").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count.get()>=1){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"售出了"+count.getAndDecrement()+"张票");
				}
			}
		}, "窗口二").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (count.get()>=1){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"售出了"+count.getAndDecrement()+"张票");
				}
			}
		}, "窗口三").start();
	}
}

volatile或者不管是Juc并发包下的api虽好,但面试官至少会让写一种加锁的方法,所以锁机制才是最重要的哦,其他的是锦上添花!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值