你是如何调用 wait()方法的?使用 if 块还是循环?为什么?
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
// The standard idiom for using the wait method
synchronized (obj) {
while (condition does not hold)
obj.wait(); // (Releases lock, and reacquires on wakeup)
... // Perform action appropriate to condition
}
10. 线程的run方法和start方法
run方法
只是thread类的一个普通方法,若直接调用程序中依然只有主线程这一个线程,还要顺序执行,依然要等待run方法体执行完毕才可执行下面的代码。
run就是正常的方法调用
start方法
用start方法来启动线程,是真正实现了多线程。调用thread类的start方法来启动一个线程,此时线程处于就绪状态,一旦得到cpu时间片,就开始执行run方法。
ReadWriteLock(读写锁)
写写互斥 读写互斥 读读并发, 在读多写少的情况下可以提高效率
wait与notify、notifyall一起用
14. sleep与wait的异同点
wait
can be "woken up" by another thread callingnotify
on the monitor which is being waited on whereas asleep
cannot. Also await
(andnotify
) must happen in a blocksynchronized
on the monitor object whereassleep
does not:
- sleep是Thread类的静态方法, wait来自object类
- sleep方法短暂停顿不释放锁, wait方法条件等待要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。
- wait, notify, notifyall必须在同步代码块中使用, sleep可以在任何地方使用
- 都可以抛出InterruptedException
Simply put,wait() is an instance method that’s used for thread synchronization.
It can be called on any object, as it’s defined right on_java.lang.Object, _but it can only be called from a synchronized block. It releases the lock on the object so that another thread can jump in and acquire a lock.
On the other hand,Thread.sleep()_is a static method that can be called from any context.Thread.sleep()_pauses the current thread and does not release any locks.
When we use the_sleep()_method, a thread gets started after a specified time interval, unless it is interrupted.
Forwait(), the waking up process is a bit more complicated. We can wake the thread by calling either the_notify()_or_notifyAll() _methods on the monitor that is being waited on.
UsenotifyAll()_instead of_notify()_when you want to wake all threads that are in the waiting state. Similarly to the_wait()_method itself,_notify(), and_notifyAll()_have to be called from the synchronized context.
用 wait-notify 写一段代码来解决生产者-消费者问题?
记住在同步块中调用 wait() 和 notify()方法,如果阻塞,通过循环来测试等待条件。
23. 用 Java 写一个线程安全的单例模式(Singleton)?
当我们说线程安全时,意思是即使初始化是在多线程环境中,仍然能保证单个实例。Java 中,使用枚举作为单例类是最简单的方式来创建线程安全单例模式的方式。参见我整理的单例的文章6种单例模式的实现以及double check的剖析
Java 中,编写多线程程序的时候你会遵循哪些最佳实践?
这是我在写Java 并发程序的时候遵循的一些最佳实践:
a)给线程命名,这样可以帮助调试。
b)最小化同步的范围,而不是将整个方法同步,只对关键部分做同步。
c)如果可以,更偏向于使用 volatile 而不是 synchronized。
d)使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通信,如 BlockingQueue,CountDownLatch 及 Semeaphore。
e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
25. 说出至少 5 点在 Java 中使用线程的最佳实践。
这个问题与之前的问题类似,你可以使用上面的答案。对线程来说,你应该:
a)对线程命名
b)将线程和任务分离,使用线程池执行器来执行 Runnable 或 Callable。
c)使用线程池
在多线程环境下,SimpleDateFormat 是线程安全的吗?
不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。
Java 中 Runnable 和 Callable 有什么不同
Runnable和 Callable 都代表那些要在不同的线程中执行的任务。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增加的。它们的主要区别是 Callable 的 call () 方法可以返回值和抛出异常,而 Runnable 的 run ()方法没有这些功能。
如何在两个线程间共享数据?
通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构
为什么 wait, notify 和 notifyAll 这些方法不在 thread 类里面
一个很明显的原因是 JAVA 提供的锁是对象级的而不是线程级的。如果线程需要等待某些锁那么调用对象中的 wait ()方法就有意义了。如果 wait ()方法定义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于 wait,notify 和 notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。
什么是线程池? 为什么要使用它?
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。
如何写代码来解决生产者消费者问题?
在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。比较低级的办法是用 wait 和 notify 来解决这个问题,比较赞的办法是用 Semaphore 或者 BlockingQueue 来实现生产者消费者模型。
如何避免死锁?
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
Java 中活锁和死锁有什么区别?
活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
有三个线程 T1,T2,T3,怎么确保它们按顺序执行
可以用线程类的 join ()方法。具体操作是在T3的run方法中调用t2.join(),让t2执行完再执行t3;T2的run方法中调用t1.join(),让t1执行完再执行t2。这样就按T1,T2,T3的顺序执行了
果你提交任务时,线程池队列已满。会发会生什么?
这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么 ThreadPoolExecutor’s submit ()方法将会抛出一个 RejectedExecutionException 异常。
Java 线程池中 submit () 和 execute ()方法有什么区别
两个方法都可以向线程池提交任务,execute ()方法的返回类型是 void,它定义在 Executor 接口中, 而 submit ()方法可以返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。
可重入锁
可重入锁:如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。
public synchrnozied void test() {
xxxxxx;
test2();
}
public synchronized void test2() {
yyyyy;
}
在上面代码段中,执行 test 方法需要获得当前对象作为监视器的对象锁,但方法中又调用了 test2 的同步方法。
如果锁是具有可重入性的话,那么该线程在调用 test2 时并不需要再次获得当前对象的锁,可以直接进入 test2 方法进行操作。
如果锁是不具有可重入性的话,那么该线程在调用test2前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次获得。
如果锁是不具有可重入性特点的话,那么线程在调用同步方法、含有锁的方法时就会产生死锁。