编程篇

1.设计一个LinkedHashMap。

2.使用Java编写一个死锁程序,并且能够自动跳出死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.me.ioc;

import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
* Created by zjw on 2017/8/9.
*/
public class DeadLockTest {

public static void main(String[] args) {
Thread th1 = new Thread(new DeadLock(true));
Thread th2 = new Thread(new DeadLock(false));

th1.setName("线程1");
th2.setName("线程2");
th1.start();
th2.start();

}


static class DeadLock implements Runnable {


private final static Object o1 = new Object();
private final static Object o2 = new Object();
private final static Semaphore a1 = new Semaphore(1);
private final static Semaphore a2 = new Semaphore(1);

boolean lockFlag;

DeadLock(boolean lockFlag) {
this.lockFlag = lockFlag;
}

@Override
public void run() {
try {
if (lockFlag) {
if (a1.tryAcquire(1, TimeUnit.SECONDS)) {
synchronized (o1) {
try {
System.out.println(Thread.currentThread().getName() + new Date().toString() + " Lock 锁住 o1");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (a2.tryAcquire(1, TimeUnit.SECONDS)) {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + new Date().toString() + "Lock 锁住 o2");
}
} else {
System.out.println(Thread.currentThread().getName() + new Date().toString() + "Lock 锁 o2 失败");
}
}
} else {
System.out.println(Thread.currentThread().getName() + new Date().toString() + "Lock 锁 o1 失败");
}
a1.release();
a2.release();
} else {
if (a1.tryAcquire(1, TimeUnit.SECONDS)) {
synchronized (o2) {
try {
System.out.println(new Date().toString() + " Lock 锁住 o1");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (a2.tryAcquire(1, TimeUnit.SECONDS)) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + new Date().toString() + "Lock 锁住 o2");
}
} else {
System.out.println(Thread.currentThread().getName() + new Date().toString() + "Lock 锁 o2 失败");
}
}
} else {
System.out.println(Thread.currentThread().getName() + new Date().toString() + "Lock 锁 o1 失败");
}
a1.release();
a2.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

3.用Java实现读取特定格式的大文件数据写入redis节点,要求速度库、数据准确、redis写入压力可控。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.me.ioc;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class RedisTest {
public static void main(String[] args) {
Jedis redis = new Jedis("120.26.137.224", 6379, 400000);
Map<String, String> map = new HashMap<>();
redis.select(8);
// redis.flushDB();

long start = System.currentTimeMillis();
//直接hmset
for (int i = 0; i< 1000000; i ++) {
map.clear();
map.put("k_" + i, "v_" + i);
redis.hmset("key_" + i, map);
}
long end = System.currentTimeMillis();

//使用pipeline hmset
Pipeline pipeline = redis.pipelined();
start = System.currentTimeMillis();
for (int i = 0; i< 1000000; i ++) {
map.clear();
map.put("k_" + i, "v_" + i);
redis.hmset("key_" + i, map);
}
pipeline.sync();
end = System.currentTimeMillis();
System.out.println("dbsize:[" + redis.dbSize() + "] .. ");
System.out.println("hmset with pipeline used [" + (end - start) / 1000 + "] seconds ..");


//hmget
Set<String> keys = redis.keys("*");

//直接使用Jedis hgetall
start = System.currentTimeMillis();
Map<String,Map<String,String>> result = new HashMap<>();
for(String key : keys) {
result.put(key, redis.hgetAll(key));
}
end = System.currentTimeMillis();
System.out.println("result size:[" + result.size() + "] ..");
System.out.println("hgetAll without pipeline used [" + (end - start) / 1000 + "] seconds ..");

//使用pipeline hgetall
Map<String,Response<Map<String,String>>> responses = new HashMap<>(keys.size());
result.clear();
start = System.currentTimeMillis();
for(String key : keys) {
responses.put(key, pipeline.hgetAll(key));
}
pipeline.sync();

for(String k : responses.keySet()) {
result.put(k, responses.get(k).get());
}
end = System.currentTimeMillis();
System.out.println("result size:[" + result.size() + "] ..");
System.out.println("hgetAll with pipeline used [" + (end - start) / 1000 + "] seconds ..");


redis.disconnect();
}
}

简答题

4.Java中的volatile关键字的作用。

volatile 关键字作用是,使系统中所有线程对该关键字修饰的变量共享可见,可以禁止线程的工作内存对volatile修饰的变量进行缓存。
http://www.jb51.net/article/93844.htm

5.线程之间如何通信。

同步方式synchorized,即共享变量,synchororized修改加锁的对象上(方法上)。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
轮询方式。多个线程根据传入的构造参数,轮询判断满足的条件,条件字段用volatile修饰,保证内存可见性。
wait/notify机制,也是synchorized,但是这个用线程上,一般用于run()的实现内部。线程调用wait() 放弃CPU,并进入阻塞状态。—不像②while轮询那样占用CPU。wait(),notify(),notifyAll()都必须使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步 才具有锁。
管道通信就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信。分布式系统中说的两种通信机制:共享内存机制和消息通信机制。感觉前面的①中的synchronized关键字和②中的while轮询 “属于” 共享内存机制,由于是轮询的条件使用了volatile关键字修饰时,这就表示它们通过判断这个“共享的条件变量“是否改变了,来实现进程间的交流。而管道通信,更像消息传递机制,也就是说:通过管道,将一个线程中的消息发送给另一个。

6.为什么wait()、notify()、notifyAll()等方法放在Object类中,而不是Thread类中?

简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。

专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
Java的每个对象中都有一个锁(monitor,也可以成为监视器)并且wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法。

7.简要阐述Java BID和NIO的原理和区别,如果可以的话选择其中一种简单实现客户端、服务端程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// NIOServer.java
package com.me.ioc;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
public void init() throws IOException {
Charset charset = Charset.forName("UTF-8");
// 创建一个选择器,可用close()关闭,isOpen()表示是否处于打开状态,他不隶属于当前线程
Selector selector = Selector.open();
// 创建ServerSocketChannel,并把它绑定到指定端口上
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(7777), 1024);
// 设置为非阻塞模式, 这个非常重要
server.configureBlocking(false);
// 在选择器里面注册关注这个服务器套接字通道的accept事件
// ServerSocketChannel只有OP_ACCEPT可用,OP_CONNECT,OP_READ,OP_WRITE用于SocketChannel
server.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
selector.select(1000);
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
//如果key对应的Channel包含客户端的链接请求
// OP_ACCEPT 这个只有ServerSocketChannel才有可能触发
key = it.next();
// 由于select操作只管对selectedKeys进行添加,所以key处理后我们需要从里面把key去掉
it.remove();
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 得到与客户端的套接字通道
SocketChannel channel = ssc.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
//将key对应Channel设置为准备接受其他请求
key.interestOps(SelectionKey.OP_ACCEPT);

}

if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
String context = "";
try {
int readByte = channel.read(buffer);
if (readByte > 0) {
buffer.flip();
byte bytes[] = new byte[buffer.remaining()];
buffer.get(bytes);
context += new String(bytes);
System.out.println(context);
doWrite(channel);
}
// 写完就把状态关注去掉,否则会一直触发写事件(改变自身关注事件)
key.interestOps(SelectionKey.OP_READ);
}catch (Exception e) {
//如果捕获到该SelectionKey对应的Channel时出现了异常,即表明该Channel对于的Client出现了问题
//所以从Selector中取消该SelectionKey的注册
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
}

}

private void doWrite(SocketChannel channel) throws IOException {
byte req[] = "服务器已接收".getBytes();
ByteBuffer buffer = ByteBuffer.allocate(req.length);
buffer.put(req);
buffer.flip();
channel.write(buffer);
if (buffer.hasRemaining()) {
System.out.println("Send 2 Service successed");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// NIOClient.java
package com.me.ioc;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;

public class NIOClient {
// 创建一个套接字通道,注意这里必须使用无参形式
private Selector selector = null;
static Charset charset = Charset.forName("UTF-8");

private volatile boolean stop = false;
public ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(8);
public void init() throws IOException {
selector = Selector.open();
SocketChannel channel = SocketChannel.open();
// 设置为非阻塞模式,这个方法必须在实际连接之前调用(所以open的时候不能提供服务器地址,否则会自动连接)
channel.configureBlocking(false);
if (channel.connect(new InetSocketAddress("127.0.0.1", 7777))) {
channel.register(selector, SelectionKey.OP_READ);
//发送消息
doWrite(channel, "66666666666");
} else {
channel.register(selector, SelectionKey.OP_CONNECT);
}

while (!stop){
selector.select(1000);
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
SocketChannel sc = (SocketChannel) key.channel();
// OP_CONNECT 两种情况,链接成功或失败这个方法都会返回true
if (key.isConnectable()) {
// 由于非阻塞模式,connect只管发起连接请求,finishConnect()方法会阻塞到链接结束并返回是否成功
// 另外还有一个isConnectionPending()返回的是是否处于正在连接状态(还在三次握手中)
if (channel.finishConnect()) {
/* System.out.println("准备发送数据");
// 链接成功了可以做一些自己的处理
channel.write(charset.encode("I am Coming"));
// 处理完后必须吧OP_CONNECT关注去掉,改为关注OP_READ
key.interestOps(SelectionKey.OP_READ);*/
sc.register(selector, SelectionKey.OP_READ);
doWrite(channel, "7777777777777");
}else {
//链接失败,进程推出
System.exit(1);
}
}

if (key.isReadable()) {
//读取服务端的响应
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);
String context = "";
if (readBytes > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
context += new String(bytes);
stop = true;
} else if (readBytes < 0) {
//对端链路关闭
key.channel();
sc.close();
}
System.out.println(context);
key.interestOps(SelectionKey.OP_READ);
}
}

}
}

private void doWrite(SocketChannel channel, String data) throws IOException {
byte[] req = data.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(req.length);
buffer.put(req);
buffer.flip();
channel.write(buffer);
if (buffer.hasRemaining()) {
System.out.println("Send 2 client successed");
}
}
}

8.解释同步和异步、阻塞和非阻塞的区别,并从以下四种IO模型中人选一种阐述其原理,同步阻塞IO、同步非阻塞IO、IO多路复用、异步IO的原理。

同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
当一个异步过程调用发出后,调用者不会立刻得到结果。实际处理这个调用的部件是在调用发出后,通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞/非阻塞, 它们是程序在等待消息(无所谓同步或者异步)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

同步阻塞形式:效率是最低的,拿上面的例子来说,就是你专心排队,什么别的事都不做。实际程序中就是未对fd 设置O_NONBLOCK 标志位的read/write 操作,
异步阻塞形式:如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发,也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;

异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息被触发时被阻塞.比如select 函数,假如传入的最后一个timeout 参数为NULL,那么如果所关注的事件没有一个被触发,程序就会一直阻塞在这个select 调用处.

同步非阻塞形式:实际上是效率低下的,想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;很多人会写阻塞的read/write 操作,但是别忘了可以对fd 设置O_NONBLOCK 标志位,这样就可以将同步操作变成非阻塞的了;

异步非阻塞形式:效率更高,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换.

I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流

9.请结合自身实际工作情况,说出三点与Java代码、性能优化有关的案例。

排错题

10.service.java文件:

1
2
3
4
5
6
7
8
9
10
public class Service {
private static Service service = null;
private static List<Sender> discardList = new ArrayList<Sender>();
private int discardNum = 2;
private AtomicInteger count = new AtomicInteger();

public static Service getService() {
return null;
}
}

11.GeTui.java文件:

1
2
3
4
5
public class GeTui {
public static void send() {
System.out.println("push the world!");
}
}

TestGeTui.java:

1
2
3
4
5
6
public class TestGeTui {
public static void main(String[] args) {
GeTui geTui = null;
geTui.send();
}
}