meidi
最近觉得某些公司的选择题也是很基础,非常值得总结回味。今天做了美的的笔试,20道选择题(单选14+6多选)。特此记录如下(部分忘了烦请见谅):
1. 是我昨晚刚刚总结的List,Set,Map的区别;
需要注意的是Map不是继承了Collection接口;
2. 关于并发编程的问题;
感觉对并发这一块不是特别熟,题目上描述了缓存导致可见性问题,线程切换带来的原子性问题;编译优化带来的有序性问题等等,不过自己仔细想想也还是能够想出来的。
关于这个问题,大家可以参考以下三篇文档:
- 【杂谈】高速缓存一致性与可见性
- 我的另一篇博客Java之先行发生原则与volatile关键字详解
可见性、原子性和有序性问题:并发编程Bug的源头(很详细,从根源说起,强推)
3.Spring事务的默认传播方式与隔离级别
Spring事务传播方式:
主要控制当前的事务如何传播到另外的事务中
- NESTED
如果当前存在事务,则在嵌套事务内执行。
如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。
外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚
- NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
- NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- REQUIRED
支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择(默认)
- REQUIREDS_NEW
新建事务,如果当前存在事务,把当前事务挂起。
- SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
- MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。
Spring事务隔离级别:
主要定义事务与事务之间在数据库读写方面的控制范围
主要解决脏读、不可重复读、虚读三个问题
ISOLATION_DEFAULT 默认级别
ISOLATION_READ_UNCOMMITED
事务最低的隔离级别,充许别外一个事务可以看到这个事务未提交的数据,
会产生脏读,不可重复读和幻像读
ISOLATION_COMMITED
保证一个事务修改的数据提交后才能被另外一个事务读取,可以避免脏读出现,
解决了脏读问题,但是可能会出现不可重复读和幻像读
ISOLATION_REPEATABLE_READ
保证一个事务不能读取另一个事务未提交的数据外可以防止脏读,不可重复读
但是可能出现幻像读
ISOLATION_SERIALIZABLE
花费最高代价但是最可靠的事务隔离级别。
事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读
参考链接:事务的传播方式
4. yieid()与sleep()的区别
在这里我拓展一下:看看sleep()、wait()、yield()、join()各方法的解析。
1 sleep()
使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会执行, 同时sleep函数不会释放锁资源。
sleep没有优先级的区别,即:可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会。
2 yield()
只是使当前线程重新回到可执行状态,所以执行yield()线程有可能在进入到可执行状态后马上又被执行。
只能使同优先级的线程有执行的机会。同样, yield()也不会释放锁资源。
sleep和yield的联系与区别:
联系:yield()方法和sleep()方法类似,也不会释放“锁标志”。
区别:
- 执行sleep()方法后,线程转入阻塞(blocked)状态,而yield()方法只是使当前线程重新回到可执行状态即(就绪状态),所以执行yield()的线程有可能在进入到可执行状态后马上又被执行;
- sleep可以使优先级低的线程得到执行的机会, 而yield只能使同优先级或者更高优先级的线程有执行的机会;
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
- sleep()方法比yield()具有更好的可移植性。
参见代码:
1 public class ThreadYieldOne implements Runnable {
2 public String name;
3 public void run() {
4 for (int i = 0; i < 10; i++) {
5 Thread.yield(); // (2) Thread.sleep(100);
6 System.out.println(name + " :" + i);
7 }
8 }
9 }
10
11 public static void main(String[] args) {
12 ThreadYieldOne one = new ThreadYieldOne();
13 ThreadYieldOne two = new ThreadYieldOne();
14 one.name = "one";
15 two.name = "two";
16 Thread t1 = new Thread(one);
17 Thread t2 = new Thread(two);
18 t1.setPriority(Thread.MAX_PRIORITY);
19 t2.setPriority(Thread.MIN_PRIORITY); // (1) t2.setPriority(Thread.MAX_PRIORITY);
20 t1.start();
21 t2.start();
22 }
执行结果:
one :0 one :1 one :2 one :3 one :4 one :5 one :6 one :7 one :8 one :9
two :0 two :1 two :2 two :3 two :4 two :5 two :6 two :7 two :8 two :9
从上可看出:当使用yield()是优先级高的先执行,执行完毕后再开始执行优先级低的;
注:
- (1) 处 如果放开注释掉t2.setPriority(Thread.MIN_PRIORITY); , 则执行结果将会改变: 如下: one和two 交替打印。
one :0 one :1 one :2 one :two :0 two :1 two :2 two :3 one :4 one :5 one :3 two :4 two :5 two :6 two :6 one :7 one :8 one :9
7 two :8 two :9
- (2)处 如果放开并注释掉Thread.yield(); , 则执行结果也是one 和two 交替打印, 并且, 它不受(1)处的影响。
3 . stop()
这个方法可以中止一个正在运行的线程, 但这样的方法并不安全。 强列建议不要使用此函数来中断线程。
注: stop方法是立即停止当前线程, 这样停止的后果是导致stop后的语句无法执行, 有可能资源未释放. 列或者在同步块中调用此方法会导致同步数据会不完整. 所以需禁用此方法. 由于stop方法的特释性, 将不给出示范代码。
4. Interrupt()
一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作。线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序。
Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
两个解释为什么会相冲突呢, 一个解释是可以中断一个线程, 一个解释说不会中断一个正在运行的线程。仔细看会发现其中的奥秒, interrupt方法不会中断一个正在运行的线程,就是指线程如果正在运行的过程中,去调用此方法是没有任何反应的。为什么呢, 因为这个方法只是提供给 被阻塞的线程, 即当线程调用了.Object.wait, Thread.join, Thread.sleep三种方法之一的时候, 再调用interrupt方法, 才可以中断刚才的阻塞而继续去执行线程。
// 情况1. 不会中断线程.
public class guoInterrupt extends Thread {
public boolean stop = false;
public static void main(String[] args) throws InterruptedException {
guoInterrupt t1 = new guoInterrupt();
System.out.println("app is starting");
t1.start();
Thread.sleep(3000);
System.out.println("Interrupting t1....");
t1.interrupt();
Thread.sleep(3000);
System.out.println("app is end");
t1.stop = true;
System.exit(0);
}
public void run() {
while(!this.stop) {
System.out.println("t1 running...........");
long time = System.currentTimeMillis();
while ( (System.currentTimeMillis()-time < 1000) && (!stop) ) {};
}
System.out.println("t1 is end");
}
}
输出结果:
app is starting
t1 running...........
t1 running...........
t1 running...........
t1 running...........
Interrupting t1....
t1 running...........
t1 running...........
t1 running...........
app is end
t1 is end
结果说明:当调用了interrupt时, t1线程仍在执行, 并没有中断线程. 直到main扫行结束后, 改t1的stop值时 t1线程才执行结束.
//情况2: interrupt中断线程.
public class guoInterrupt extends Thread {
public boolean stop = false;
public static void main(String[] args) throws InterruptedException {
guoInterrupt t1 = new guoInterrupt();
System.out.println("app is starting");
t1.start();
Thread.sleep(3000);
System.out.println("Interrupting t1....");
t1.stop = true; //就是这一句语句的位置的作用;
t1.interrupt();
Thread.sleep(3000);
System.out.println("app is end");
System.exit(0);
}
public void run() {
while(!this.stop) {
System.out.println("t1 running...........");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("t1 is Interrupting......");
}
}
System.out.println("t1 is end");
}
}
执行结果:
app is starting
t1 running...........
t1 running...........
t1 running...........
Interrupting t1....
t1 is Interrupting......
t1 is end
app is end
结果说明: 当执行了 t1.interrupt();方法时, 线程立即产生了一个.InterruptedException 异常。
5 . join()
当join(0)时等待一个线程直到它死亡(即等待线程执行完毕);
当join(1000)时等待一个线程1000纳秒(即执行线程并不一定执行完毕),后回到主线程继续执行。
代码示例:
public static void main(String[] args) {
ThreadJoin t = new ThreadJoin();
try {
t.start();
Thread.sleep(1000);
System.out.println("main join start");
t.join(0); // (1)
System.out.println("main join end");
} catch (InterruptedException e) {
e.printStackTrace();
}
} public class ThreadJoin extends Thread {
public void run() {
System.out.println("join start");
try {
Thread.sleep(9000);
for (int i = 0; i < 5; i++) {
System.out.println("sub thread:" + i);
}
} catch (InterruptedException e) {
}
System.out.println("join end");
}
}
输出结果:
- 当(1)处 为t.join(0)时 等待t线程执行完之后回到main函数的主线程继续处理;
结果如下:
join start
main join start
sub thread:0
sub thread:1
sub thread:2
sub thread:3
sub thread:4
join end
main join end
- 当(1)处 为t.join(1000)时,main函数的主线程等待t经程1000纳秒后继续执行;
结果如下:
join start
main join start
main join end
sub thread:0
sub thread:1
sub thread:2
sub thread:3
sub thread:4
join end
注: join函数为线程安全函数, 即同步函数。
也就是说上面的例子, 当ThreadJoin类的run用synchronized锁住时, t.join方法将得不到锁资源而等待更长的时间。
【注意:以上都是thread类的方法!!!!】
6. wait()
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
wait()方法需要和notify()及notifyAll()两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象的锁。注意,这些是Object类的方法,而不是Thread类的方法。
wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。
多线程的同步:
- synchronized;
- wait;
- notify;
线程的同步需要依靠上面两个函数和一个同步块实现。
在这里以读写为例:
有一个缓冲区, 由两个线程进行操作, 一个写线程, 一个读线程. 写线程完成对缓冲区的写入, 读线程完成对线程的读取, 只有当缓冲区写入数据时, 读线程才可以读取, 同样. 只有当缘冲区的数据读出时, 写线程才可以向缘冲区写入数据。
代码如下:
public class ThreadWrite extends Thread {
public StringBuffer buffer;
public ThreadWrite(StringBuffer buffer) {
this.buffer = buffer;
}
public void run() {
synchronized (this.buffer) {
for (int i = 0; i < 5; i++) {
if (!"".equals(this.buffer.toString())) {
try {
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Write start");
this.buffer.append("123");
this.buffer.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("Write end");
}
}
}
} public class ThreadRead extends Thread {
public StringBuffer buffer;
public ThreadRead(StringBuffer buffer) {
this.buffer = buffer;
}
public void run() {
synchronized (buffer) {
for (int i = 0; i < 5; i++) {
if ("".equals(this.buffer.toString().trim())) {
try {
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("read start");
System.out.println(this.buffer.toString());
buffer.delete(0, buffer.toString().length());
buffer.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("read end");
}
}
}
} public class GuoSynchronized {
public static void main(String[] args) {
StringBuffer bufer = new StringBuffer("");
ThreadWrite write = new ThreadWrite(bufer);
ThreadRead read = new ThreadRead(bufer);
read.start();
write.start();
}
}
执行结果:
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
结果说明: 这个结果不管执行多少次, 都会是相同的结果, 即多线程同步的特点。
注: synchronized 关键字的同步, 在两个线程之间必须是同一个对象, 即 对同一个对象进行同步访问。
- 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
- 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
- waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
以上总结如下:
参考:
1. sleep(),wait(),yield()和join()方法的区别
2. sleep()、wait()、yield()、join()方法 解析
5. 双重校验单例中volatile关键字的用处
volatile关键字具有两种特性:
- 保证此变量对所有线程的可见性
- 禁止指令重排序优化。
而在双重校验单例中,起作用的是第二种。
场景:
1. 状态标记量:使用volatile来修饰状态标记量,使得状态标记量对所有线程是实时可见的;
2. 双重检测机制实现单例:普通的双重检测机制在极端情况,由于指令重排序会出现问题,通过使用volatile来修饰instance,禁止指令重排序,从而可以正确的实现单例。
6. 创建对象的个数以及判断是否相等
- ==比较的是两个对象的内存地址;
- equals()比较的是两个对象的内容;
public static void main(String[] args) {
String s1 = "mei";//pool中没有,新new一个String对象在堆中,再创建一个对象"mei",并把“mei”放入常量池中;
String s2 = new String("mei");//pool中有,故只需要创建一个对象放在堆中即可;
String s3 = "m" + "ei";
System.out.println((s1 == s2) + " || " + s1.equals(s2));//false||true
System.out.println(s1 == s3);//true
System.out.println((s1 == s1.intern())+ " || " + (s1.equals(s1.intern())));//true||true
System.out.println((s2 == s2.intern()) + " || " + (s2.equals(s2.intern())));//false||true
/*
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
*/ }
输出结果:
false || true
true
true || true
false || true
meidi的更多相关文章
- 20,Django contenttypes 应用
contenttypes 是Django内置的一个应用,可以追踪项目中所有app和model的对应关系,并记录在ContentType表中. 1.创建一个项目 2.数据库迁移,生成默认表. 3.存着所 ...
- day106 支付功能与优惠券功能 contentype
https://blog.csdn.net/Ayhan_huang/article/details/78626957 一.ContenType from django.db import models ...
随机推荐
- CodeMonkey少儿编程第3章 times循环
目标 了解程序由哪三种基本的结构组成 了解循环的概念 掌握times的结构与用法 三种基本结构 计算机程序由三种最基本的结构组成,它们分别是: 顺序结构 循环结构 选择结构 千万不要被这些陌生的术语给 ...
- JS复习笔记一:冒泡排序和二叉树列
在这里逐步整理一些JS开发的知识,分享给大家: 一:冒泡排序 使用场景:数组中根据某个值得大小来对这个数组进行整体排序 难度:简单 原理:先进行循环,循环获取第一至倒数第二的范围内所有值,对当前值与下 ...
- SUGA
愿试炼的终点是花开万里 愿以渺小启程伟大结束 ----闵玧其
- Elasticsearch从入门到放弃:浅谈算分
今天来聊一个 Elasticsearch 的另一个关键概念--相关性算分.在查询 API 的结果中,我们经常会看到 _score 这个字段,它就是用来表示相关性算分的字段,而相关性就是描述一个文档和查 ...
- 实用 nginx.conf 用法大全
服务器拒绝非GET方式请求保障安全性,因为 DELETE.POST.PUT 是可以修改数据的. Nginx 解决方案 在 nginx.conf 配置文件的网站配置区域中添加如下代码片段: 非 GET ...
- go语言rpc学习
rpc 就是 远程过程调用 指的是调用远端服务器上的程序的方法整个过程. rpc 理论 RPC技术在架构设计上有四部分组成,分别是:客户端.客户端存根.服务端.服务端存根. 客户端:服务调用发 ...
- C++学习之STL(二)String
1.assign assign方法可以理解为先将原字符串清空,然后赋予新的值作替换. 返回类型为 string类型的引用.其常用的重载也有下列几种: a. string& assign ( c ...
- Spark DataSource Option 参数
Spark DataSource Option 参数 1.parquet 2.orc 3.csv 4.text 5.jdbc 6.libsvm 7.image 8.json 9.xml 9.1读选项 ...
- Kubernetes --(k8s)入门
k8s 简介: 什么是k8s? Kubernetes (k8s)是Google开源的容器集群管理系统(谷歌内部:Borg).在Docker技术基础上,为容器化的应用提供部署运行.资源调度.服务发现和动 ...
- 8.Cisco DHCP中继详解
1.网络中的终端通过发送DHCP广播的方式来获取IP地址信息.由于VLAN隔离广播,当终端与DHCP服务器不在同一广播域时,就需要用到DHCP中继. 2.DHCP服务的原理概述: DHCP服务器想要给 ...