面试中手写的与线程有关的一些算法
单例的各种模式
生产者消费者模式
死锁以及如何解决
多线程环境下的加与减法
交替打印等场景题
2020/9/5开始整理 争取2020/9/10整理完
单例模式(并发中如何实现安全的并发)
面试的时候可能要手写单例模式,下面进行了总结;单例模式的核心是如何确保创建对象时是线程安全的
饿汉式
私有化构造器
类初始化时,立即加载对象
提供获取对象的方法
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:23
*
* 饿汉式单例
*
* 1、构造器私有化
* 2、上来创建一个静态对象
* 3、一个方法返回创建的静态对象
*/
public class SingleDemon01 {
//1、构造器私有化
private SingleDemon01(){
}
//思考为什么是静态对象?
//2、类初始化时,立即加载对象:
//这一步和JVM类记载有关,jvm保证静态变量在类加载的时候是线程安全的
//JVM类加载过程: 加载 验证 准备 解析 初始化
private static SingleDemon01 singleDemon01= new SingleDemon01();
//3、提供获取对象的方法,没有synchrionized,效率高
public static SingleDemon01 getInstance(){
return singleDemon01;
}
}
懒汉式
私有化构造器
类初始化不用立即加载对象
提供获取对象的方法,需要synchronized,效率低
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:33
* 饿汉式
*/
public class SingleDemon02 {
//1、构造器私有化
private SingleDemon02(){
}
//思考为什么是静态对象?
//2、类初始化时,不立即加载对象:
private static SingleDemon02 instance= null;
//3、提供获取对象的方法,有synchrionized,效率低
public static synchronized SingleDemon02 getInstance(){
if(instance==null){
instance = new SingleDemon02();
}
return instance;
}
}
DCL(double checking )
构造器私有化
类初始化是,不用立即加载对象
提供获取对象的方法,而不是在方法内部进行双重检测
为了避免指令重拍,可以采用volitale关键字
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:38
*
* 双重检测懒汉式:(DCL)_
*/
public class SingleDemon03 {
//1、构造器私有化
private SingleDemon03(){
}
//思考为什么是静态对象?
//2、类初始化时,不立即加载对象:
//为了避免指令重拍:可以在变量前加上 volitale关键字
private static volitale SingleDemon03 instance= null;
//3、提供获取对象的方法,双重检测
public static SingleDemon03 getInstance(){
if(instance==null){
synchronized (SingleDemon03.class) {
if(instance==null)
instance = new SingleDemon03();
}
}
return instance;
}
}
枚举单例(线程安全,调用效率高,不能延时加载)推荐
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:56
* 枚举
*/
public enum SingleDemon05 {
//枚举里纯天然就是单例的
INSTANCE;
public SingleDemon05 getInstance(){
return INSTANCE;
}
}
生产者与消费者模式
面试时,有可能要手写
生产这与消费者模式
- 可以使用原生的
Object.wait()与Object.notify
方法- 可以使用
ReentranLock生成的 Condition 进行Condition.await()与Condition.signal
方法- 在JUC包中,使用到了
ArrayBlockingQueue
也使用到了生产者与消费者模式
下面进行剖析
生产者消费者模式主要角色分析:生产者:用于提交用户请求,提取用户任务,并装入内存缓冲区
消费者:在内存缓冲区中提取并处理任务
内存缓冲区:缓存生产者提交的任务或数据,供消费者使用
任务:生产者向内存缓冲区中提交的数据结构
Main:使用生产者和消费者的客户端
生产者消费者编写的时候抓住:判断等待
,业务逻辑
,通知
下面以一个线程加1,一个线程减1的生产者消费者模式进行分析
Object方式实现生产者消费者模式
package com.zj.ProductAndConsumer;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/30 0030 20:57
*/
public class Case1 {
public static void main(String[] args) {
Data data1 = new Data();
new Thread(new Runnable() {
@Override
public void run() {
try {
while(true)
data1.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
while(true)
data1.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
// 判断等待,业务,通知
class Data{ // 数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
while (number!=0){ //0
// 等待
this.wait();
}
//业务
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (number==0){ // 1
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我-1完毕了
this.notifyAll();
}
}
ReentranLock实现生产者与消费者
package com.zj.ProductAndConsumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
#
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/30 0030 21:04
*/
public class Case2 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
data.decrement();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
data.increatment();
}
}
}).start();
}
}
class Data2{
private int num = 0;
//用ReentranLock取代Synchronized
private ReentrantLock lock = new ReentrantLock();
//用Condition绑定这个lock
Condition condition = lock.newCondition();
//当数据为0的时候进行加1(业务逻辑判断)
//对数据加1
//通知
public void increatment() {
lock.lock();
//逻辑判断
try {
while(num==1){
condition.await();
}
//业务处理
num++;
System.out.println(Thread.currentThread().getName()+"--"+num);
//唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement() {
//逻辑判断
lock.lock();
//逻辑判断
try {
while(num==0){
condition.await();
}
//业务处理
num--;
System.out.println(Thread.currentThread().getName()+"--"+num);
//唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
并发中的死锁
死锁的代码
如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA = new Thread(new Runnable() {
public void run() {
synchronized (a) {
try {
System.out.println("now i in threadA-locka");
Thread.sleep(1000l);
synchronized (b) {
System.out.println("now i in threadA-lockb");
}
} catch (Exception e) {
// ignore
}
}
}
});
Thread threadB = new Thread(new Runnable() {
public void run() {
synchronized (b) {
try {
System.out.println("now i in threadB-lockb");
Thread.sleep(1000l);
synchronized (a) {
System.out.println("now i in threadB-locka");
}
} catch (Exception e) {
// ignore
}
}
}
});
threadA.start();
threadB.start();
}
上述如何解决死锁的问题呢? 以确定的顺序或区域所,破坏的是
循环等待条件
死锁出现的条件与如何破坏死锁
一般来说,要出现死锁问题需要满足一下条件
互斥条件
:一个资源每次只能被 一个线程使用请求与保持条件
:一个进程因请求资源被阻塞时,对已经获得的资源保持不放不剥夺条件
:进程已获得资源,在未使用完之前,不能强行剥夺循环等待
:若干进程之间形成一种头尾相接的循环等待资源的关系
介绍一下怎么发现死锁以及如何解决
由于死锁极难通过人工的方式查出来,因此JDK 提供了命令来检测某个java进程中心线程的情况,并排查有没有死锁。上面命令呢? jps , 用来查看java 程序的进程号,当然在 Linux 中也可以通过别的方式获取, jstack
进程号命令则可以答应对应进程的栈信息,并找到死锁。
实现线程安全的加法
使用锁
使用AtomicInteger
使用锁
package com.zj.MultiThread.add;
/**
* Created by ZhouJian on 2020/9/6
* 线程安全的加法:
* 加锁
*/
public class SafeAdd {
int i =0;
public synchronized void add() throws InterruptedException {
i++;
Thread.sleep(10);
}
public static void main(String[] args) throws InterruptedException {
SafeAdd safeAdd = new SafeAdd();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000;i++){
try {
safeAdd.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000;i++){
try {
safeAdd.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
Thread.sleep(50000);
System.out.println(safeAdd.i);
}
}
使用AtomicInteger
package com.zj.MultiThread.add;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ZhouJian on 2020/9/6
* 原子类
*/
public class SafeAdd01 {
AtomicInteger i =new AtomicInteger(0);
public void add() throws InterruptedException {
i.incrementAndGet();
Thread.sleep(10);
}
public static void main(String[] args) throws InterruptedException {
SafeAdd01 safeAdd = new SafeAdd01();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000;i++){
try {
safeAdd.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000;i++){
try {
safeAdd.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
Thread.sleep(50000);
System.out.println(safeAdd.i);
}
}
多线程交替执行
- 方案一:Synchronized+wait/notify (
一个标志
,或者一个数
)- 方案二:Lock+Condition(一个标志+一个数)
* 这种注意需要手动加锁与释放锁
* 同时这种方式可以等待多个条件,更加灵活
一个线程加1一个线程减1多个线程交替执行
多个线程交替执行(三个线程轮流打印ABC)
使用Lock+Condition的机制
package com.zj.MultiThread;
import java.util.Locale;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by ZhouJian on 2020/9/6
*/
public class PrintABC {
// 一把锁
Lock lock = new ReentrantLock();
// 每个线程都有自己的等待条件
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
// 计算num 进行等待的
int num = 0;
public void printA(){
lock.lock();
try {
while (num!=0) conditionA.await();
num = 1;
System.out.print("A");
// 将下一个线程环形
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
while (num!=1) conditionB.await();
num = 2;
System.out.print("B");
conditionC.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
while (num!=2) conditionC.await();
num = 0;
System.out.print("C");
System.out.println();
conditionA.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
PrintABC printABC = new PrintABC();
new Thread(new Runnable() {
@Override
public void run() {
while(true)
printABC.printA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true)
printABC.printB();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true)
printABC.printC();
}
}).start();
}
}
打印100以内的所有数据,线程1打印奇数,打印5个数切换到线程2,线程2打印偶数,打印5个然后切换到线程1
== synchronized对线程对共享资源的互斥访问,满足条件则执行;不满足则等待满足条件==
package com.zj.MultiThread.JiaoTiZhiXing;
/**
* Created by ZhouJian on 2020/9/6
*/
public class PrintNumber {
int i = 0;
// 两个线程交替执行
boolean flag = true;
// 打印5个奇数
public synchronized void printJiShu() throws InterruptedException {
// 不满足条件需要等待,等待在锁上即可
while (flag==false) this.wait();
i++;
for(int t=0;t<5;t++){
System.out.print(i+" ");
i=i+2;
}
System.out.println();
flag = true;
this.notify();
}
public synchronized void printOuShu() throws InterruptedException {
// 不满足条件需要等待,等待在锁上即可
while (flag==true) this.wait();
i=i-1;
for(int t=0;t<5;t++){
System.out.print(i+" ");
i=i+2;
}
System.out.println();
flag = false;
this.notify();
}
public static void main(String[] args) {
PrintNumber printNumber = new PrintNumber();
// 打印奇数
new Thread(new Runnable() {
@Override
public void run() {
while (printNumber.i<100) {
try {
printNumber.printJiShu();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
// 打印偶数
new Thread(new Runnable() {
@Override
public void run() {
while (printNumber.i<100) {
try {
printNumber.printOuShu();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
多个线程同时启动
借助 CyclicBarrier与CountDownLatch,好好看看底层源码
package com.zj.MultiThread.ThreadStart;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* Created by ZhouJian on 2020/9/6
* 多个线程同时启动
*/
public class TestCyclicBarrier {
CyclicBarrier cyclicBarrier;
public TestCyclicBarrier(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
public void doWork(){
System.out.println(Thread.currentThread().getName()+"做好了准备");
try {
cyclicBarrier.await();// 等待其他线程准备好
System.out.println(Thread.currentThread().getName()+"开始执行"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
TestCyclicBarrier testCyclicBarrier = new TestCyclicBarrier(cyclicBarrier);
// 开启5个线程
for (int i=0;i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
testCyclicBarrier.doWork();
}
}).start();
}
}
}