sleep()和wait()的区别属于老生常谈了,大部分Java面试或者笔试都会问到。标准的答案是:java
其上第一点,线程阻塞二者都会释放cpu资源,这一点很重要。想一想如下执行的代码会有区别吗?面试
// 代码段1
while (true) {
System.out.println("Hello World");
}
// 代码段2
while (true) {
System.out.println("Hello World");
Thread.sleep(1);
}
复制代码
上面的代码段1,是一个死循环,执行该代码电脑风扇就呼呼响了,cpu占用也提高。而代码段二,也是使用了while(true)包裹,但cpu占用却不会明显提高。缓存
从上面咱们知道,若是while(true)中使用了能使得线程阻塞的代码,那么程序将一直运行但cpu不会高占用。JDK源码中大量使用了这个设计。好比,延迟线程池ScheduledThreadPoolExecutor
,其使用了DelayedWorkQueue队列,将task按执行时间排序,排在队头的第一个执行。安全
同时,使用for(;;)死循环,比较目标时间和当前时间的毫秒差值delay,若是delay小于等于0则执行该task,不然awaitNanos(delay)阻塞线程。这样就避免了for(;;)占用cpu占满cpu。源码以下:数据结构
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
复制代码
synchronized关键字咱们再熟悉不过了,其做用有两点:多线程
使用synchronized同步块或者同步方法时,只有一个线程能进入该区域,叫作线程同步。synchronized配置wait()和notify()使用,能够在两个或多个线程之间协调资源,叫作线程协做,这也是为何wait()和notify()须要在synchronized中使用的缘由。this
那synchronized的原理是什么呢?--监视器monitoratom
synchronized后通常带有锁对象,即synchronized(this)或者默认的锁对象。synchronized底层由Java对象头和monitor监视器实现。spa
监视器指令有ACC_SYNCHRONIZED和monitorenter、monitorexit。线程
而每一个Object对象在对象头都有指向ObjectMonitor数据结构的指针,以下图,ObjectMonitor中包含owner保存当前执行的线程,EntryList保存执行到synchronized的线程,waitSet保存执行了wait()方法的线程,线程是以ObjectWaiter形式封装保存到队列的,count表示重入次数,所以synchronized是可重入锁。
总结以上的内容,就是synchronized围绕对象锁,对象锁维护了EntryList用于线程同步,WaitSet用于线程协做。
synchronized用起来比较繁琐难用。别担忧,JDK给咱们提供了JUC包中的ReentrantLock,是synchronized的完美代替品。具体的内容能够看相关资料,这里不展开讨论。
其实多线程的问题,涉及到了JMM内存模式,多线程会发生如下几个问题:
解决了上面的三个问题,你的程序就是线程安全的了。而volatile能够保证有序性和可见性。
volatile底层由内存屏障实现,内存屏障是cpu的指令,其禁止在内存屏障先后的指令中插入其余指令,保证了有序性,同时内存屏障强制刷缓存,保证了可见性。单例的DCL(Double Check Lock)是典型的使用volatile的场景,由于new一个实例须要三步:分配内存、初始化、指针指向,这三步不能保证有序性,可使用volatile解决。
JUC包简直是多线程开发的神器,JUC主要的概念是CAS,AQS,基本全部类都是构建在其上。JUC包基本能够说是用来替代volatile和synchronized关键字的,其中atomic包用来替代volatile,lock包用来替代synchronized,lock中的condition实现了await(),signal()用来替代synchronized的wait(),notify。JUC包中的BlockingQueue是使用Condition来实现的。线程池则是构建在BlockingQueue队列上。