五道Android手写题,动脑动嘴还得动手
近期为了准备找工作面试,在上一篇文章里面整理了几个Android方面比较常见的面试题,一些八股文,但是现在面试跟以前不太一样了,八股文可能在一场面试里面占的比例不是很多,甚至人家不问,问的多的都是些项目经验啊,或者某个问题怎么解决的,能看出你一定水平的,或者一些编程题,让你现场写代码或者上机敲代码,这种面试题才可以检验出面试者能力究竟如何,所以这次我也整理了一些编程题,希望可以对我,对看过这篇文章的人有所帮助
使用kotlin来实现单例模式
以前让写单例模式的时候,都是要求用java写,但这几年随着kotlin成为了Android开发的主要语言,像这种手写单例模式也必须用kotlin写出来,下面就来看下分别使用五种方式来实现kotlin版本的单例模式
饿汉式

饿汉式当初用java实现的时候就已经代码量很少了,而在kotlin中,代码量更少,将一个类的class
关键字改成object
就是kotlin的饿汉式写法
懒汉式

懒汉式跟java里面的懒汉式就比较相似了,将构造器声明为private
防止外界访问,函数getInstance()
用来获取实例,关键字Synchronized
用来保护线程安全
双重检查锁

关键字volatile
使得变量instance
可见性,防止指令重排,并且在创建instance
的时候给instance
加锁,确保线程安全
静态内部类

这种方式是让一个静态内部类持有单例实例,然后利用类加载机制保证线程安全
枚举类

由于枚举本身只会被创建一次,所以它是线程安全的
文件名排序
这是之前在某一家外企面试的时候他们出的笔试题,原题具体内容忘记了,大致的一个意思如下所示

乍一看,就是一个字符串排序的,感觉太easy了,但是如果你仔细看一下希望输出的结果以后,会一时之间找不出规律,这个既不像是对数字的排序,也不像是对字符串的排序,这个规律似乎很难找,但是我们朝输出结果较靠后的那部分观察一下,会发现,到是的确是按照字符串排序的,唯一区别是,靠后那部分字符串里面是有的带数字,有的不带数字,而比较靠前的字符串里面是都有数字的,而且将字母部分去除的话会发现,它的确是按照数字大小来排序,那么,这个规律就被我们找到了,排序的代码如下

首先比较字符串中前后字符串里面的数字部分,如果都有数字而且数字不一样,那么比较数字,如果数字相同就比较字符串,如果前后字符串里面存在不含数字的字符串,那么也是比较字符串
如何实现一个线程安全的计数器
我们知道多线程是有线程安全隐患的,举个例子,如果想要多线程实现一个计数器,如果不考虑线程安全的话会写成下面这样

这个预期是想要打印出200,但是实际的结果却不是这样

实际只是199,说明上面这个操作是线程不安全的,那么该怎么改呢?
原子类型
第一种方式可以使用原子类型AtomicInteger
,在每个线程中使用incrementAndGet
方法来自增,代码如下


使用原子类型就能在多线程中保证线程安全,原理是因为其内部使用了CAS锁,将当前值与目标值先做比较,如果一致就更新当前值为目标值,如果不一致则不做修改
synchronized
第二种方式就是使用同步函数synchronized
,代码如下

也可以保证线程安全的输出最终值

如何实现多线程顺序执行
多线程中,线程之间是通过抢占资源来执行的,所以我们无法确定哪个线程先执行哪个线程后执行,比如下面这段代码

这里新建了10个线程并且都让它们执行,打印的日志将线程的下标值同时打印出来,我们可以看下最终结果是怎么样的

很明显线程并不是按照创建顺序来执行的,那么如果我们想要让线程按照创建的顺序来执行,该怎么做呢?
使用join
第一个方法使用Thread
类里面自带的join
方法,我们可以先看下join
方法的源码都干了啥事情

首先这个方法是同步的,所以可以保证只有一个线程会执行它,然后先方法内,无论是给join
设置了还是没设置延迟时间,它都是会在线程执行中,也就是isAlive
是true
的时候一直wait
,直到isAlive
不为true
的时候,才不会等待,那么在这里我们只要在启动线程后执行一下join
方法,那么每个线程都会等到前一个线程执行完毕再去执行,将代码更改后试一下


跟预期的一样,线程都是按照顺序执行了
使用FutureTask
有人说不想使用join
,就是想单纯的执行线程,然后让它们顺序执行,那该怎么做呢?这里就要使用FutureTask
了,FutureTask
其实也是个Runnable
,所以它也可以作为参数传给Thread
,而创建一个FutureTask
需要使用Callable
,Callable
与Runnable
其实差不多,主要区别是Callable
可以将执行结果返回,而想要获取执行结果的话就要使用FutureTask
的get
方法,这个get
方法就是让我们顺序执行线程的关键,看下get
里面的源码

光看到awaitDone
的名字就知道,这个get
方法是会等到线程执行完毕后才会释放资源,知道这些后,具体代码就知道怎么写了

执行结果如下

使用CountDownLatch
现在我们已经用两种方式来实现线程顺序执行了,但是有人会说,我就想用Runnable
,难道就不能执行吗?也是可以的,我们可以使用CountDownLatch
,这是个计数器,构造函数中的入参表示需要计数的数量,调用await
方法使得当前线程等待,调用countDown
方法让计数器减一,当计数变为0的时候,就释放当前线程,那么使用计数器的代码如下

因为这里有十个线程,所以需要9个CountDownLatch
,因为第一个线程不需要await
,最后一个线程不需要countDown
,最终执行结果也是顺序执行线程

如何让三个线程交替打印ABC
想要实现三个线程交替打印ABC,那么就需要使用信号量SemapPhore
来实现,每个信号量控制一个线程,当某个信号量被获取时候,也就是调用acquire
方法,对应线程就会执行,并且将可用信号量减1,这样其他信号量由于拿不到可用信号,就会陷入阻塞阶段,而当信号量调用release
方法的时候,可用信号量就会加一,可用信号量又有了,其他信号量开始获取,具体代码如下

运行结果如下

总结
五道手写题写完了,个人觉得手写题还是重在一个思路,并不存在什么标准答案,有些手写题可以有多个方式去实现,不过想要锻炼出看到个手写题就有思路这件事,还是要去网上多看一些手写题,算法的解题过程,多看看别人是如何解题的,学习一下那种思考方式,所以终究还是离不开一个积累,温水煮青蛙,慢工出细活,好的offer急不来的
转载自:https://juejin.cn/post/7388842078799003657