目录
前言
“实战面经”是本专栏的第二个部分,本篇博文是第九篇博文,如有需要,可:
一、并发编程
线程A随机每隔(10 - 200ms)按顺序生成1到100的数字(共100个),放到某个地方。线程B、C、D即时消费这些数据,线程B消费所有被3整除的数,线程C消费所有被5整除的数,其它的由线程D进行消费。线程B、C、D消费这些数据时在控制台中打印出来,要求按顺序打印这些数据。
- 商品集合类(消息队列)
public class Goods {
private final Queue<Integer> queue;
private int productCount;
public Goods() {
queue = new LinkedList();
productCount = 0;
}
public void produce(String pName) throws InterruptedException {
while (++productCount <= 100) {
synchronized (this) {
int delay = (int) (new Random(System.currentTimeMillis()).nextFloat() * 190) + 10;
queue.offer(productCount);
this.wait(delay);
this.notifyAll();
}
}
}
public void consume(String cName, int base) throws InterruptedException {
while (true) {
synchronized (this) {
if (queue.size() > 0 && (base == -1 && queue.peek() % 3 != 0 && queue.peek() % 5 != 0 || base != -1 && queue.peek() % base == 0)) {
int product = queue.poll();
System.out.println(consumerName + " consumed " + product);
this.notifyAll();
} else {
this.wait();
}
}
}
}
}
- 生产者
public class Producer implements Runnable{
private final Goods goods;
private final String name;
public Producer(Goods goods, String name){
this.goods = goods;
this.name = name;
}
@Override
public void run() {
try {
goods.produce(name);
} catch (InterruptedException ex) {
Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
- 消费者
public class Consumer implements Runnable{
private final Goods goods;
private final int base;
private final String name;
public Consumer(Goods goods, int base, String name){
this.goods = goods;
this.base = base;
this.name = name;
}
@Override
public void run() {
try {
goods.consume(name, base);
} catch (InterruptedException ex) {
Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
- 测试类
public class Starter {
private static Goods goods;
public static void main(String[] args) {
goods = new Goods();
Executor threadPool = Executors.newFixedThreadPool(4);
threadPool.execute(new Producer(goods, "A"));
threadPool.execute(new Consumer(goods, 3, "B"));
threadPool.execute(new Consumer(goods, 5, "C"));
threadPool.execute(new Consumer(goods, -1, "D"));
}
}
注: 最重要的是生产者中连续的以下两句:
this.wait(delay);
this.notifyAll();
休眠随机时间,同时唤醒所有的消费线程。
二、Linux中的文件权限
使用root权限输入ls –al可以查看文件的详细信息,如:
drwxr-xr-- 1 root root 5238 Aug 13 15:50 a.txt
以每两个空格为间隔共分为7个部分:
-
第一个字符表示文件类型,d是文件夹,-是文件,|是链接文件,后面9个字符,顺序是[读、写、执行] * 3,代表文件拥有者、同小组的其他人、小组以外的其他人对该文件是否拥有相应的权限,没有权限表示为-。
-
有多少个文件名链接到此文件。
-
文件的拥有者
-
文件所属的用户组
-
文件的大小
-
文件最后一次被修改的时间
-
文件名
三、Linux文件权限中的一些点
ls
命令的-a
参数可以看到隐藏文件,即名称以.
开头的文件。- 必须具有
x
权限才能进入一个文件夹。 - root用户可以读取权限为[
----------
]的文件,如账号管理文件/etc/shadow
.
四、修改文件属性和权限的指令
- 修改文件所属的组:
chgrp group file
- 修改文件拥有者:
chown user[:group] file
- 修改文件权限:
1) 权限分数: 以rwx
从右往左表示从低位到高位,换做3个二进制位,有权限就 是1
,没有就是0
,如rwx
是7
,r-x
是5
.
2) 命令语法:chmod xyz file
,xyz
即三种角色各自的权限分数,如777
五、查看文件的几种指令
- cat: 将文件内容全部打印在屏幕上,tac可以由最后一行至第一行反向打印。
- more: 一页一页翻,只能向后翻。可以按关键字向后搜索
- less: 一页一页翻,可以双向翻。可以按关键字向前或向后搜索。
- head: 文件开头的数行。
- tail: 文件末尾的数行。
六、Linux文件系统
- ext2: 索引式文件系统,不需要碎片整理
1) 超级区块: 记录文件系统的总体信息,包括inode与数据区块的总量、使用 量、剩余量等。
2) inode: 记录文件的属性、权限,一个文件占用一个inode,同时记录文件所在 的区块号。
3) 数据区块: 记录文件的内容,文件过大时会占用多个区块。 - FAT: windows系统、U盘,链式文件系统,需要进行碎片整理
- 挂载点: 将文件系统与目录树结合的操作称为挂载,挂载点一定是目录,该目录是进入该文件系统的入口。
七、inode结构
-
左边是
inode
,除了权限和属性的记录区域外,包含12个直接指针,一、二、三级指针各1个。 -
一个区块的大小为1KB时,一条区块码需要4B,则1KB可以存256个区块码。一个文件最大为 ( 12 + 256 + 25 6 2 + 25 6 3 ) ∗ 1 K B = 16 G B (12 + 256 + 256^2 + 256^3) * 1KB = 16GB (12+256+2562+2563)∗1KB=16GB.
八、硬链接与符号链接
-
硬链接: 在目录下新增一条文件名链接到某个inode号上。
1) 指令:ln source target
2) 优点: 安全,删除硬链接,inode
与区块仍然存在。
3) 限制: 不能跨文件系统、不能链接目录。 -
符号链接: 建立一个独立的文件,让数据的读取指向它链接的那个文件的文件名。相当于windows中的快捷方式。
1) 指令:ln –s source target
2) 优点: 可以跨文件系统,可以链接目录
九、进程操作文件
理解具体情况,需要了解由内核维护的3个数据结构:
- 进程级文件描述符表
- 系统级打开文件表
- 文件系统inode表
十、Linux执行用户代码
进程执行用户代码时,处于用户态,当发生一些事件是会转换到内核态。
-
系统调用: 用户进程主动要求切换到内核态的一种方式,用户进程通过系统调用申请操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
-
异常: 当CPU在执行运行在用户态的程序时,发现了某些事件不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就到了内核态。
-
外围设备的中断: 当外围设备完成用户请求的操作之后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条将要执行的指令转而去执行中断信号的处理程序,如果先执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了有用户态到内核态的切换。
十一、在Linux系统中,开一个Terminal往一个文件中写入程序,执行,系统都做了什么?
-
进程维护文件描述符表,每一个表项都通过文件指针指向系统的打开文件表,打开文件表中每一项都代表一个打开的文件,它通过
inode
指针指向inode
表中的记录。 -
读写文件时找到文件对应的
inode
去读取文件的权限以及属性,如果用户权限符合,进而找到数据块进行读写。 -
执行用户代码时,进程进入用户态,如发生了中断、异常或者程序调用了
Linux
的系统调用,则进程变为内核态。
十二、Java基础类型和封装类型的区别
-
基础类型只能进行值传递,封装类型可以进行引用传递。
-
封装类型有一些基础类型不具备的方法,如
toString
、hashCode
等,这些方法在使用集合类时会用到。 -
基础类型的内存是在栈上分配的,封装类型的内存是在堆上分配的。在栈中分配内存的效率比堆中高。
根据实际应用的需要进行选择
十三、HTTP与HTTPS
-
HTTPS
工作在SSL
(Secure Sockets Layer,安全套接字层)上,是更加安全的,端口采用443
,安全套接层工作在TCP
上对传输数据进行加密: -
密钥对: 使用公钥加密,私钥解密。
-
单向认证过程:
1) 客户端向服务端发送SSL版本信息;
2) 服务端向客户端发送SSL版本信息、证书和公钥;
3) 客户端从信任的证书库比对服务端的证书,合法认证成功;
4) 客户端发送支持的加密方案给服务端;
5) 服务端选择加密程度最高的方案以明文方式发送给客户端;
6) 客户端生成随机码,作为对称加密的密钥,用服务端的公钥加密后发送给服务端;
7) 服务端用私钥进行解密,获得对称加密密钥;
8) 双方使用对称加密方法对数据进行加密通信。 -
双向认证的不同之处:
3) 之后: 客户端将自己的证书和公钥发给服务端,服务端对客户端进行认证,获 得客户端公钥;
5) 加密方案用客户端公钥加密后发送给客户端。
后记
这篇面经虽然题目不多,但是一上来就做一道编程题,让人很难招架。