java while题目_Java的一些题目

速度StringBuilder>StringBuffer>String,StringBuffer线程安全

线程安全的集合有:Vector、Stack、HashTable、ConcurrentHashMap、

CopyOnWriteXXX(如CopyOnWriteArrayList)

ClassLoader

程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。

Java默认提供的三个ClassLoader。BootStrap ClassLoader:称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。

Extension ClassLoader:称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。

App ClassLoader:称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。

ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的

Java ClassLoader原理

synchronized lock

当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所有无法访问该对象的其他synchronized方法。

当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。因为非synchronized方法不需要获取该对象的锁。

如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型,也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

如果一个线程执行一个对象的非static synchronized方法,另一个线程执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。

需要注意的是:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

显式锁ReentrantLock则可以将锁的获得和释放分开。同时显式锁可以提供轮训锁和定时锁,同时可以提供公平锁或者非公平锁。

在ReentrantLock类中有一个重要的函数newCondition(),该函数用于获取lock上的一个条件,也就是说Condition是和Lock绑定的。Condition用于实现线程间的通信,它是为了解决Object.wait()、notify()、notifyAll()难以使用的问题。

public class MyArrayBlockingQueue {

// 数据数组

private final T[] items;

// 锁

private final Lock mLock = new ReentrantLock();

// 数组满的条件

private Condition notFull = mLock.newCondition();

// 数组空的条件

private Condition notEmpty = mLock.newCondition();

// 头部

private int head;

// 尾部

private int tail;

// 数据数量

private int count;

public MyArrayBlockingQueue(int maxSize) {

// TODO Auto-generated constructor stub

items = (T[]) new Object[maxSize];

}

public MyArrayBlockingQueue() {

// TODO Auto-generated constructor stub

this(10);

}

public void put(T t) {

mLock.lock();

try {

// 如果数据已满,等待

while (count == getCapacity()) {

System.out.println("数据已满,请等待");

notFull.await();

}

System.out.println("存入数据");

items[tail] = t;

if (++tail == getCapacity()) {

tail = 0;

}

++count;

// 唤醒等待数据的线程

notEmpty.signalAll();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} finally {

mLock.unlock();

}

}

public T take() {

mLock.lock();

try {

// 如果数组数据为空,则阻塞

while (count == 0) {

System.out.println("还没有数据,等待");

notEmpty.await();

}

System.out.println("取出数据");

T t = items[head];

items[head] = null;

if (++head == getCapacity()) {

head = 0;

}

--count;

// 唤醒添加数据的线程

notFull.signalAll();

return t;

} catch (InterruptedException e) {

// TODO: handle exception

} finally {

mLock.unlock();

}

return null;

}

public int getCapacity() {

return items.length;

}

public int size() {

mLock.lock();

try {

return count;

} finally {

mLock.unlock();

}

}

/**

* @param args

*/

public static void main(String[] args) {

// TODO Auto-generated method stub

final MyArrayBlockingQueue mQueue = new MyArrayBlockingQueue<>(

5);

new Thread(new Runnable() {

@Override

public void run() {

// TODO Auto-generated method stub

while (true) {

for(int i = 0;i < 3;i++)

mQueue.put("just");

try {

Thread.sleep(50);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

// TODO Auto-generated method stub

while (true) {

mQueue.take();

}

}

}).start();

}

}

结果打印

存入数据

存入数据

存入数据

取出数据

取出数据

取出数据

还没有数据,等待

存入数据

存入数据

存入数据

取出数据

取出数据

取出数据

还没有数据,等待

当时看到这段代码我就想到一个问题:如果一个线程lock()对象后被挂起还没有unlock,那么另外一个线程就拿不到锁了(lock()操作会挂起),那么就无法通知(notify)前一个线程,这样岂不是“死锁”了?

再回头看代码,不管take()还是put(),在进入lock.lock()后唯一可能释放锁的操作就是await()了。也就是说await()操作实际上就是释放锁,然后挂起线程,一旦条件满足就被唤醒,再次获取锁!

await源码如下

public final void await() throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

Node node = addConditionWaiter();

int savedState = fullyRelease(node);

int interruptMode = 0;

while (!isOnSyncQueue(node)) {

LockSupport.park(this);

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)

break;

}

if (acquireQueued(node, savedState) && interruptMode != THROW_IE)

interruptMode = REINTERRUPT;

if (node.nextWaiter != null)

unlinkCancelledWaiters();

if (interruptMode != 0)

reportInterruptAfterWait(interruptMode);

}

完整的await()操作是安装如下步骤进行的:

将当前线程加入Condition锁队列。特别说明的是,这里不同于AQS的队列,这里进入的是Condition的FIFO队列。后面会具体谈到此结构。进行2。

释放锁。这里可以看到将锁释放了,否则别的线程就无法拿到锁而发生死锁。进行3。

自旋(while)挂起,直到被唤醒或者超时或者CACELLED等。进行4。

获取锁(acquireQueued)。并将自己从Condition的FIFO队列中释放,表明自己不再需要锁(我已经拿到锁了)。

介绍Condition的数据结构。我们知道一个Condition可以在多个地方被await*(),那么就需要一个FIFO的结构将这些Condition串联起来,然后根据需要唤醒一个或者多个(通常是所有)。所以在Condition内部就需要一个FIFO的队列。

//conditon 的两个属性

private transient Node firstWaiter;

private transient Node lastWaiter;

这两个节点就是描述一个FIFO的队列。我们再结合前面提到的节点(Node)数据结构。我们就发现Node.nextWaiter就派上用场了!nextWaiter就是将一系列的Condition.await*串联起来组成一个FIFO的队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值