面试王牌 JAVA并发
并发
Table of Contents
- 1 什么是并发问题。
- 2多线程死锁问题
- 2 java中synchronized的用法
- 3 Java中的锁与排队上厕所。
- 4 何时释放锁?
- 5 Lock的使用
- 6 利用管道进行线程间通信
- 7 阻塞队列
- 8 使用Executors、Executor、ExecutorService、ThreadPoolExecutor
- 9 并发流程控制
- 10 并发3定律
- 11 由并发到并行
- java的同步机制,大概是通过:
1.synchronized;
2.Object方法中的wait,notify;
3.ThreadLocal机制
来实现的, 其中synchronized有两种用法:
1.对类的方法进行修饰
2.synchronized(对象)的方法进行修饰
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。 当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信 的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所 以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程 序,使程序更加易读、简洁。
1什么是并发问题。
多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。
银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为 1000+100-50=1050。这就是典型的并发问题。如何解决?可以用锁。
2多线程死锁问题
前天俺们谈到了加锁,但是在使用加锁的同时又会带来一个问题,就是死锁。 什么叫死锁? 所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。 那么为什么会产生死锁呢? 1.因为系统资源不足。 2.进程运行推进的顺序不合适。 3.资源分配不当。 学过操作系统的朋友都知道:产生死锁的条件有四个: 1.互斥条件:所谓互斥就是进程在某一时间内独占资源。 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
例如:
死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
分析死锁产生的原因不难看出是由访问共享资源顺序不当所造成的,下面写一个造成线程死锁的例子,希望能对大家理解多线程死锁问题有进一步的理解!如果有人需要编写多线程的系统,当操作共享资源时一定要特别的小心,以防出现死锁的情况!
package com.ljq.test;
public class RunnableTest implements Runnable {
private int flag = 1; private Object obj1 = new Object(), obj2 = new Object();
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) { synchronized (obj1) { System.out.println("我已经锁定obj1,休息0.5秒后锁定obj2去!");
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (obj2) { System.out.println("1"); } } }
if (flag == 0) { synchronized (obj2) { System.out.println("我已经锁定obj2,休息0.5秒后锁定obj1去!");
try { Thread.sleep(500); } catch (InterruptedException e) {
e.printStackTrace(); } synchronized (obj1) {
System.out.println("0"); } } } } public static void main(String[] args)
{ RunnableTest run01 = new RunnableTest();
RunnableTest run02 = new RunnableTest();
run01.flag = 1; run02.flag = 0;
Thread thread01 = new Thread(run01);
Thread thread02 = new Thread(run02);
System.out.println("线程开始喽!");
thread01.start();
thread02.start(); } }
运行结果如下:
这样就产生了死锁,这是我们过多的使用同步而产生的。我们在java中使用synchonized的时候要考虑这个问题,如何解决死锁,大家可以从死锁的四个条件去解决,只要破坏了一个必要条件,那么我们的死锁就解决了。在java中使用多线程的时候一定要考虑是否有死锁的问题哦。
2 java中synchronized的用法
- 用法1
public class Test{
public synchronized void print(){
....;
}
}某线程执行print()方法,则该对象将加锁。其它线程将无法执行该对象的所有synchronized块。
- 用法2
public class Test{
public void print(){
synchronized(this){//锁住本对象
...;
}
}
}同用法1, 但更能体现synchronized用法的本质。
- 用法3
public class Test{
private String a = "test";
public void print(){
synchronized(a){//锁住a对象
...;
}
}
public synchronized void t(){
...; //这个同步代码块不会因为print()而锁定.
}
}执行print(),会给对象a加锁,注意不是给Test的对象加锁,也就是说 Test对象的其它synchronized方法不会因为print()而被锁。同步代码块执行完,则释放对a的锁。
为了锁住一个对象的代码块而不影响该对象其它 synchronized块的高性能写法:
public class Test{
private byte[] lock = new byte[0];
public void print(){
synchronized(lock){
...;
}
}
public synchronized void t(){
...;
}
} - 静态方法的锁
public class Test{
public synchronized static void execute(){
...;
}
}效果同
public class Test{
public static void execute(){
synchronized(TestThread.class){
...;
}
}
}
3 Java中的锁与排队上厕所。
锁就是阻止其它进程或线程进行资源访问的一种方式,即锁住的资源不能被其它请求访问。在JAVA中,sychronized关键字用来对一个对象加锁。比如:
public class MyStack {
int idx = 0;
char [] data = new char[6]; public synchronized void push(char c) {
data[idx] = c;
idx++;
} public synchronized char pop() {
idx--;
return data[idx];
} public static void main(String args[]){
MyStack m = new MyStack();
/**
下面对象m被加锁。严格的说是对象m的所有synchronized块被加锁。
如果存在另一个试图访问m的线程T,那么T无法执行m对象的push和
pop方法。
*/
m.pop();//对象m被加锁。
}
}
Java的加锁解锁跟多个人排队等一个公共厕位完全一样。第一个人进去后顺手把门从里面锁住,其它人只好排队等。第一个人结束后出来时,门才会打开(解锁)。轮到第二个人进去,同样他又会把门从里面锁住,其它人继续排队等待。
用厕所理论可以很容易明白: 一个人进了一个厕位,这个厕位就会锁住,但不会导致另一个厕位也被锁住,因为一个人不能同时蹲在两个厕位里。对于Java 就是说:Java中的锁是针对同一个对象的,不是针对class的。看下例:
MyStatck m1 = new MyStack();
MyStatck m2 = new Mystatck();
m1.pop();
m2.pop();
m1对象的锁是不会影响m2的锁的,因为它们不是同一个厕位。就是说,假设有 3线程t1,t2,t3操作m1,那么这3个线程只可能在m1上排队等,假设另2个线程 t8,t9在操作m2,那么t8,t9只会在m2上等待。而t2和t8则没有关系,即使m2上的锁释放了,t1,t2,t3可能仍要在m1上排队。原因无它,不是同一个厕位耳。
Java不能同时对一个代码块加两个锁,这和数据库锁机制不同,数据库可以对一条记录同时加好几种不同的锁,请参见:
http://hi.baidu.com/dapplehou/blog/item/b341a97744fe6616b151b9a3.html
4何时释放锁?
一般是执行完毕同步代码块(锁住的代码块)后就释放锁,也可以用wait()方式半路上释放锁。wait()方式就好比蹲厕所到一半,突然发现下水道堵住了,不得已必须出来站在一边,好让修下水道师傅(准备执行notify的一个线程)进去疏通马桶,疏通完毕,师傅大喊一声: "已经修好了"(notify),刚才出来的同志听到后就重新排队。注意啊,必须等师傅出来啊,师傅不出来,谁也进不去。也就是说notify后,不是其它线程马上可以进入封锁区域活动了,而是必须还要等notify代码所在的封锁区域执行完毕从而释放锁以后,其它线程才可进入。
这里是wait与notify代码示例:
public synchronized char pop() {
char c;
while (buffer.size() == 0) {
try {
this.wait(); //从厕位里出来
} catch (InterruptedException e) {
// ignore it...
}
}
c = ((Character)buffer.remove(buffer.size()-1)).
charValue();
return c;
} public synchronized void push(char c) {
this.notify(); //通知那些wait()的线程重新排队。注意:仅仅是通知它们重新排队。
Character charObj = new Character(c);
buffer.addElement(charObj);
}//执行完毕,释放锁。那些排队的线程就可以进来了。
再深入一些。
由于wait()操作而半路出来的同志没收到notify信号前是不会再排队的,他会在旁边看着这些排队的人(其中修水管师傅也在其中)。注意,修水管的师傅不能插队,也得跟那些上厕所的人一样排队,不是说一个人蹲了一半出来后,修水管师傅就可以突然冒出来然后立刻进去抢修了,他要和原来排队的那帮人公平竞争,因为他也是个普通线程。如果修水管师傅排在后面,则前面的人进去后,发现堵了,就wait,然后出来站到一边,再进去一个,再wait,出来,站到一边,只到师傅进去执行notify. 这样,一会儿功夫,排队的旁边就站了一堆人,等着notify.
终于,师傅进去,然后notify了,接下来呢?
1. 有一个wait的人(线程)被通知到。
2. 为什么被通知到的是他而不是另外一个wait的人?取决于JVM.我们无法预先
判断出哪一个会被通知到。也就是说,优先级高的不一定被优先唤醒,等待
时间长的也不一定被优先唤醒,一切不可预知!(当然,如果你了解该JVM的
实现,则可以预知)。
3. 他(被通知到的线程)要重新排队。
4. 他会排在队伍的第一个位置吗?回答是:不一定。他会排最后吗?也不一定。
但如果该线程优先级设的比较高,那么他排在前面的概率就比较大。
5. 轮到他重新进入厕位时,他会从上次wait()的地方接着执行,不会重新执行。
恶心点说就是,他会接着拉巴巴,不会重新拉。
6. 如果师傅notifyAll(). 则那一堆半途而废出来的人全部重新排队。顺序不可知。
Java DOC 上说,The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object(当前线程释放锁前,唤醒的线程不能去执行)。
这用厕位理论解释就是显而易见的事。
5 Lock的使用
用synchronized关键字可以对资源加锁。用Lock关键字也可以。它是JDK1.5中新增内容。用法如下:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100];
int putptr, takeptr, count; public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
} public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
(注:这是JavaDoc里的例子,是一个阻塞队列的实现例子。所谓阻塞队列,就是一个队列如果满了或者空了,都会导致线程阻塞等待。Java里的 ArrayBlockingQueue提供了现成的阻塞队列,不需要自己专门再写一个了。)
一个对象的lock.lock()和lock.unlock()之间的代码将会被锁住。这种方式比起synchronize好在什么地方?简而言之,就是对wait的线程进行了分类。用厕位理论来描述,则是那些蹲了一半而从厕位里出来等待的人原因可能不一样,有的是因为马桶堵了,有的是因为马桶没水了。通知(notify)的时候,就可以喊:因为马桶堵了而等待的过来重新排队(比如马桶堵塞问题被解决了),或者喊,因为马桶没水而等待的过来重新排队(比如马桶没水问题被解决了)。这样可以控制得更精细一些。不像synchronize里的wait和notify,不管是马桶堵塞还是马桶没水都只能喊:刚才等待的过来排队!假如排队的人进来一看,发现原来只是马桶堵塞问题解决了,而自己渴望解决的问题(马桶没水)还没解决,只好再回去等待(wait),白进来转一圈,浪费时间与资源。
Lock方式与synchronized对应关系:
Lock | await | signal | signalAll |
synchronized | wait | notify | notifyAll |
注意:不要在Lock方式锁住的块里调用wait、notify、notifyAll
6利用管道进行线程间通信
原理简单。两个线程,一个操作PipedInputStream,一个操作 PipedOutputStream。PipedOutputStream写入的数据先缓存在Buffer中,如果 Buffer满,此线程wait。PipedInputStream读出Buffer中的数据,如果Buffer 没数据,此线程wait。
jdk1.5中的阻塞队列可实现同样功能。
- 例1 这个例子实际上只是单线程,还谈不上线程间通信,但不妨一看。
http://hi.baidu.com/ecspell/blog/item/7b02d3133ab555005aaf53f5.html
package io;
import java.io.*;
public class PipedStreamTest {
public static void main(String[] args) {
PipedOutputStream ops=new PipedOutputStream();
PipedInputStream pis=new PipedInputStream();
try{
ops.connect(pis);//实现管道连接
new Producer(ops).run();
new Consumer(pis).run();
}catch(Exception e){
e.printStackTrace();
} }
} //生产者
class Producer implements Runnable{
private PipedOutputStream ops;
public Producer(PipedOutputStream ops)
{
this.ops=ops;
} public void run()
{
try{
ops.write("hell,spell".getBytes());
ops.close();
}catch(Exception e)
{e.printStackTrace();}
}
} //消费者
class Consumer implements Runnable{
private PipedInputStream pis;
public Consumer(PipedInputStream pis)
{
this.pis=pis;
} public void run()
{
try{
byte[] bu=new byte[100];
int len=pis.read(bu);
System.out.println(new String(bu,0,len));
pis.close();
}catch(Exception e)
{e.printStackTrace();}
}
} - 例2 对上面的程序做少许改动就成了两个线程。
package io;
import java.io.*;
public class PipedStreamTest {
public static void main(String[] args) {
PipedOutputStream ops=new PipedOutputStream();
PipedInputStream pis=new PipedInputStream();
try{
ops.connect(pis);//实现管道连接
Producer p = new Producer(ops);
new Thread(p).start();
Consumer c = new Consumer(pis);
new Thread(c).start();
}catch(Exception e){
e.printStackTrace();
} }
} //生产者
class Producer implements Runnable{
private PipedOutputStream ops;
public Producer(PipedOutputStream ops)
{
this.ops=ops;
} public void run()
{
try{
for(;;){
ops.write("hell,spell".getBytes());
ops.close();
}
}catch(Exception e)
{e.printStackTrace();}
}
} //消费者
class Consumer implements Runnable{
private PipedInputStream pis;
public Consumer(PipedInputStream pis)
{
this.pis=pis;
} public void run()
{
try{
for(;;){
byte[] bu=new byte[100];
int len=pis.read(bu);
System.out.println(new String(bu,0,len));
}
pis.close();
}catch(Exception e)
{e.printStackTrace();}
}
} - 例3. 这个例子更加贴进应用
import java.io.*; public class PipedIO { //程序运行后将sendFile文件的内容拷贝到receiverFile文件中
public static void main(String args[]){
try{//构造读写的管道流对象
PipedInputStream pis=new PipedInputStream();
PipedOutputStream pos=new PipedOutputStream();
//实现关联
pos.connect(pis);
//构造两个线程,并且启动。
new Sender(pos,"c:\\text2.txt").start();
new Receiver(pis,"c:\\text3.txt").start();
}catch(IOException e){
System.out.println("Pipe Error"+ e);
}
}
}
//线程发送
class Sender extends Thread{
PipedOutputStream pos;
File file;
//构造方法
Sender(PipedOutputStream pos, String fileName){
this.pos=pos;
file=new File(fileName);
}
//线程运行方法
public void run(){
try{
//读文件内容
FileInputStream fs=new FileInputStream(file);
int data;
while((data=fs.read())!=-1){
//写入管道始端
pos.write(data);
}
pos.close();
}
catch(IOException e) {
System.out.println("Sender Error" +e);
}
}
} //线程读
class Receiver extends Thread{
PipedInputStream pis;
File file;
//构造方法
Receiver(PipedInputStream pis, String fileName){
this.pis=pis;
file=new File(fileName);
}
//线程运行
public void run(){
try {
//写文件流对象
FileOutputStream fs=new FileOutputStream(file);
int data;
//从管道末端读
while((data=pis.read())!=-1){ //写入本地文件
fs.write(data);
}
pis.close();
}
catch(IOException e){
System.out.println("Receiver Error" +e);
}
}
}
7阻塞队列
阻塞队列可以代替管道流方式来实现进水管/排水管模式(生产者/消费者).JDK1.5提供了几个现成的阻塞队列. 现在来看ArrayBlockingQueue的代码如下:
这里是一个阻塞队列
BlockingQueue<Object> blockingQ = new ArrayBlockingQueue<Object> 10;
一个线程从队列里取
for(;;){
Object o = blockingQ.take();//队列为空,则等待(阻塞)
}
另一个线程往队列存
for(;;){
blockingQ.put(new Object());//队列满,则等待(阻塞)
}
可见,阻塞队列使用起来比管道简单。
8使用Executors、Executor、ExecutorService、ThreadPoolExecutor
可以使用线程管理任务。还可以使用jdk1.5提供的一组类来更方便的管理任务。从这些类里我们可以体会一种面向任务的思维方式。这些类是:
- Executor接口。使用方法:
Executor executor = anExecutor;//生成一个Executor实例。
executor.execute(new RunnableTask1());用意:使用者只关注任务执行,不用操心去关注任务的创建、以及执行细节等这些第三方实现者关心的问题。也就是说,把任务的调用执行和任务的实现解耦。
实际上,JDK1.5中已经有该接口出色的实现。够用了。
- Executors是一个如同Collections一样的工厂类或工具类,用来产生各种不同接口的实例。
- ExecutorService接口它继承自Executor. Executor只管把任务扔进 executor()里去执行,剩余的事就不管了。而ExecutorService则不同,它会多做点控制工作。比如:
class NetworkService {
private final ServerSocket serverSocket;
private final ExecutorService pool; public NetworkService(int port, int poolSize) throws IOException {
serverSocket = new ServerSocket(port);
pool = Executors.newFixedThreadPool(poolSize);
} public void serve() {
try {
for (;;) {
pool.execute(new Handler(serverSocket.accept()));
}
} catch (IOException ex) {
pool.shutdown(); //不再执行新任务
}
}
} class Handler implements Runnable {
private final Socket socket;
Handler(Socket socket) { this.socket = socket; }
public void run() {
// read and service request
}
}ExecutorService(也就是代码里的pool对象)执行shutdown后,它就不能再执行新任务了,但老任务会继续执行完毕,那些等待执行的任务也不再等待了。
- 任务提交者与执行者通讯
public static void main(String args[])throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<String> task = new Callable<String>(){
public String call()throws Exception{
return "test";
}
};
Future<String> f = executor.submit(task);
String result = f.get();//等待(阻塞)返回结果
System.out.println(result);
executor.shutdown();
}Executors.newSingleThreadExecutor()取得的Executor实例有以下特性:
- 任务顺序执行. 比如:
executor.submit(task1);
executor.submit(task2);必须等task1执行完,task2才能执行。
- task1和task2会被放入一个队列里,由一个工作线程来处理。即:一共有2个线程(主线程、处理任务的工作线程)。
- 任务顺序执行. 比如:
- 其它的类请参考Java Doc
9并发流程控制
本节例子来自温少的Java并发教程,可能会有改动。向温少致敬。
- CountDownLatch 门插销计数器
- 启动线程,然后等待线程结束。即常用的主线程等所有子线程结束后再执行的问题。
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
final int count=10;
final CountDownLatch completeLatch = new CountDownLatch(count);//定义了门插销的数目是10 for(int i=0;i<count;i++){
Thread thread = new Thread("worker thread"+i){
public void run(){
//do xxxx
completeLatch.countDown();//减少一根门插销
}
};
thread.start();
}
completeLatch.await();//如果门插销还没减完则等待。
}JDK1.4时,常用办法是给子线程设置状态,主线程循环检测。易用性和效率都不好。
- 启动很多线程,等待通知才能开始
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
final CountDownLatch startLatch = new CountDownLatch(1);//定义了一根门插销 for (int i = 0; i < 10; i++) {
Thread thread = new Thread("worker thread" + i) {
public void run() {
try {
startLatch.await();//如果门插销还没减完则等待
} catch (InterruptedException e) { }
// do xxxx
}
};
thread.start();
}
startLatch.countDown();//减少一根门插销
}
- 启动线程,然后等待线程结束。即常用的主线程等所有子线程结束后再执行的问题。
- CycliBarrier. 等所有线程都达到一个起跑线后才能开始继续运行。
public class CycliBarrierTest implements Runnable {
private CyclicBarrier barrier; public CycliBarrierTest(CyclicBarrier barrier) {
this.barrier = barrier;
} public void run() {
//do xxxx;
try {
this.barrier.await();//线程运行至此会检查是否其它线程都到齐了,没到齐就继续等待。到齐了就执行barrier的run函数体里的内容
} catch (Exception e) { }
} /**
* @param args
*/
public static void main(String[] args) {
//参数2代表两个线程都达到起跑线才开始一起继续往下执行
CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
public void run() {
//do xxxx;
}
});
Thread t1 = new Thread(new CycliBarrierTest(barrier));
Thread t2 = new Thread(new CycliBarrierTest(barrier));
t1.start();
t2.start();
} }这简化了传统的用计数器+wait/notifyAll来实现该功能的方式。
10并发3定律
- Amdahl定律. 给定问题规模,可并行化部分占12%,那么即使把并行运用到极致,系统的性能最多也只能提高1/(1-0.12)=1.136倍。即:并行对提高系统性能有上限。
- Gustafson定律. Gustafson定律说Amdahl定律没有考虑随着cpu的增多而有更多的计算能力可被使用。其本质在于更改问题规模从而可以把Amdahl定律中那剩下的88%的串行处理并行化,从而可以突破性能门槛。本质上是一种空间换时间。
- Sun-Ni定律. 是前两个定律的进一步推广。其主要思想是计算的速度受限于存储而不是CPU的速度. 所以要充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解.
11由并发到并行
计算机识别物体需要飞速的计算,以至于芯片发热发烫,而人在识别物体时却一目了然,却并不会导致某个脑细胞被烧热烧焦(夸张)而感到不适,是由于大脑是一个分布式并行运行系统,就像google用一些廉价的linux服务器可以进行庞大复杂的计算一样,大脑内部无数的神经元的独自计算,互相分享成果,从而瞬间完成需要单个cpu万亿次运算才能有的效果。试想,如果在并行处理领域有所创建,将对计算机的发展和未来产生不可估量的影响。当然,其中的挑战也可想而知:许多的问题是并不容易轻易就“分割”的了的。
作者: Dapple Hou
Email:mmonkeyer@163.com
http://hi.baidu.com/dapplehou/blog/item/0a4c65388143b72c96ddd814.html
面试王牌 JAVA并发的更多相关文章
- 面试王牌 JAVA 多态只针对方法 不针对属性
子类是永远继承父类的非私有化方法,当子类中重写父类方法时,调用的是子类的方法,没有重写时,调用的是父类中的方法 1 多态是只针对方法,而不是属性的,但是写法上,子类重写父类的属性,编译器是不会报错的 ...
- 面试系列<3>——java并发
面试系列--java并发 一.使用线程 有三种使用线程的方法: 实现Runnable接口 实现Callable接口 继承Thread类 实现 Runnable 和 Callable 接口的类只能当做一 ...
- Java并发编程73道面试题及答案 —— 面试稳了
今天主要整理一下 Java 并发编程在面试中的常见问题,希望对需要的读者有用. 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任 ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发进阶常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.觉得内容不错 ...
- Java并发面试问题之volatile到底是什么?
本文转载自公众号:石杉的架构笔记,阅读大约需要7分钟. 一.写在前面 前段时间把几年前带过的一个项目架构演进的过程整理了一个系列出来,参见(<亿级流量架构系列专栏总结>). 不过很多同学看 ...
- Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...
- java并发面试
1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...
- Java并发面试问题,谈谈你对AQS的理解
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
随机推荐
- HDOJ1004 数组还要自己初始化
#include <iostream> #include <stdio.h> #include "string.h"using namespace std; ...
- JavaScript 实现块级作用域
(function(){ 块级作用域: })();
- Statelessness Provide credentials with the request. Each request MUST stand alone and should not be affected from previous conversation happened from same client in past.
The server never relies on information from previous requests. Statelessness As per the REST (REpres ...
- bash_action
https://stackoverflow.com/questions/12076326/how-to-install-maven2-on-redhat-linux #!/bin/bash # Tar ...
- 使用POCO发送HTTP(S)请求
POCO GitHub地址https://github.com/pocoproject/poco http_example.cpp #include <iostream> #include ...
- redis03----link 链表操作
link 链表结构 之前是操作字符串string 链表:头元素,后面一个一个的指向后面的元素.Redis内部实现了链表的结构.链表的头尾,从一个元素找到另外的元素. 链表的名字也是一个key. flu ...
- 使用proc接口例子【转】
本文转载自:http://blog.csdn.net/mike8825/article/details/52434666 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇的使用sys接口 ...
- ES6 模板编译
顾名思义,就是用反引号编写一个模板字符串, 用echo将模板转为javascrip表达式字符串, 用正则将基础字符串转为想要字符串 将代码封装在函数中返回: 注: 用到es6属性${} var tem ...
- c# 常用的面试题
2 .列举ASP.NET 页面之间传递值的几种方式. 答. 1).使用QueryString, 如....?id=1; response. Redirect().... ...
- 【转】在IDEA中创建maven项目
原文地址: https://blog.csdn.net/zzy1078689276/article/details/78732183 现在的JavaWeb项目中,绝大多数都是采用的maven结构的项目 ...