1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
答:使用join就OK了。
public class ThreeThread {
public static void main(String[] args) throws InterruptedException {
OneThread one =new OneThread();
one.setName("one");
one.start();
one.join(); //进入等待,只有one线程执行完毕,才会释放等待,才能继续往下执行。
OneThread two =new OneThread();
two.setName("two");
two.start();
two.join(); //join()这一句,就好比:thread.sleep();一样,不过比sleep好很多。
OneThread three =new OneThread();
three.setName("three");
three.start();
}
}
class OneThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
解答:join可以暂时理解为wait,或者sleep。
join源码分析:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
//isAlive()是一个本地方法,判断该线程是否存活,如果该线程还存活着,则一直等待0秒。
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可以看到join其实就是利用了wait()方法来实现的。当调用one.join()时,main()主线程被wait了,就不能继续往下执行,也就不能执行two.start()。
只有等到one线程死亡,也就是执行完毕后,wait才会被释放,继续往下执行。
2.什么是线程,什么是进程?
进程用于把资源集中到一起,而线程则是在CPU上被调度执行的实体。
进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。
线程:是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。
多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响. 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
3.线程上下文切换是什么?
即使是单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。
这就像我们同时读两本书,当我们在读一本英文的技术书籍时,发现某个单词不认识,于是便打开中英文词典,但是在放下英文书籍之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换也会影响多线程的执行速度。
更多信息查看我的文章:《java多线程-进程与线程(一)》
4.在java中wait和sleep方法的不同?
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
wait线程会被挂起,让出当前cpu调度以及资源,sleep则会根据sleep时间一直占用者cpu以及资源。
更多信息查看我的文章:《java多线程-线程间通信(七)》
5.为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
public class Handinterrupt{
public volatile boolean b=true;
public void run(){
while(b){ //通过外面b变量来中断
....
}
}
}
class MyThreadStop extends Thread {
private int temp;
public MyThreadStop(int temp) {
this.temp = temp;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("i=" + i);
if (Thread.interrupted()) { //接受停止的状态
System.out.println("线程已经停止了哦");
break;
}
}
System.out.println(Thread.currentThread().getName() + temp);
}
}
public class ThreadStopDome {
public static void main(String orgs[]) {
MyThreadStop myThread = new MyThreadStop(1);
myThread.start();
myThread.interrupt();//发起停止线程
System.out.println("运行完毕");
}
}
interrupt并不能真正的停止线程,只是将线程的状态标记为停止;需要配合Thread.interrupted()使用,这个读取线程的状态后,发现线程被停止了, 然后做一些相关操作。
public class ReentrantDome4 {
private Lock lock = new ReentrantLock();
public void serviceA() {
try {
//可中断,响应中断。这个和lock是一样的,只不过在lock()的基础上加了一个判断,用来响应线程的中断。
lock.lockInterruptibly();
Thread.sleep(500);
System.out.println("lock了。。。。。" + Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("中断了.....");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReentrantDome4 dome4 = new ReentrantDome4();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadB=" + Thread.currentThread().getId());
dome4.serviceA();
}
});
t2.start();
t2.interrupt(); //如果把这个去了,相对来说lock.lockInterruptibly()就和lock.lock()功能一样了。
}
}
lock.lockInterruptibly();加锁时判断线程是否被停止了。如果线程停止了,直接抛异常,不往下执行了。
t2.start(); //开始线程
t2.interrupt(); //停止线程
如果start已经开始了在到达lock.lockInterrup 时执行了interrupt线程才可能停止,如果已经执行到lock.locIn之后的语句了,也就无法停止了。lock.lockInterruptibly()只是在获取锁时,判断是否该线程已经停止了, 如果停止了则直接抛异常,不往下执行了。如果已经获取到锁,并且执行了,这个时候才停止,则停止是无效的。
源码:
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) //判断是否停止了
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.shutdown(); //停止线程池。线程池停止了,就是说线程池里面的线程也应该停止了。
源码:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers(); //停止线程
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//尝试获取锁,并且线程被停止了,开始执行t.interrupt
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
总结:
Java中,明确规定任何线程都不可能停止另外一个正在运行的线程。
上面我们可以看到,他并不是真正的停止了,只是根据接受到的状态,做响应的操作而已,而不是真正意义上的线程停止了。可以看到lock的可停止线程,内部原理也是使用的interrupt来操作的。
写一个简单死锁程序,或者怎么解决死锁,已经死锁的原因?
死锁一般发送在至少两把锁以上被多个线程使用而导致的,或者使用lock忘记锁解锁。
看一个synchronized死锁的问题:
class DeadThread{
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() throws Exception
{
synchronized (left)
{
System.out.println("left...start");
Thread.sleep(2000);
synchronized (right)
{
System.out.println("leftRight end!");
}
System.out.println("left end");
}
}
public void rightLeft() throws Exception
{
synchronized (right)
{
System.out.println("right...start");
Thread.sleep(2000);
synchronized (left)
{
System.out.println("rightLeft end!");
}
System.out.println("right....end");
}
}
}
public class DeadLock2 {
public static void main(String[] args) {
final DeadThread thread=new DeadThread();
Thread thread1= new Thread(new Runnable() {
@Override
public void run() {
try {
thread.leftRight();
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread.rightLeft();
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
上面发生死锁了,为什么?因为线程1,2他们几乎一起执行,线程1执行了leftRight方法,刚开始获取到了left对象锁;线程2执行了rightLeft方法,刚进入就获取到了right对象锁。当等待2s过后,线程1需要right这把锁,但是right这把锁在线程2手里,线程2s后需要left这把锁,结果left锁在线程1里面,这样就产生了死锁。
来看下使用Lock的tryLock锁避免死锁。
class LockThread {
private Lock lock = new ReentrantLock();
private Lock lock2 = new ReentrantLock();
void leftRight() throws Exception {
try {
lock.lock();
System.out.println("left...start");
Thread.sleep(2000);
boolean a = lock2.tryLock();
System.out.println("left...a=" + a);
System.out.println("left end!");
} finally {
lock.unlock();
lock2.unlock();
}
}
void rightLeft() throws Exception {
try {
lock2.lock();
System.out.println("right...start");
Thread.sleep(2000);
boolean a = lock.tryLock();
System.out.println("right...a=" + a);
System.out.println("right end!");
} finally {
lock2.unlock();
lock.unlock();
}
}
}
public class DeadLock3 {
public static void main(String[] args) {
final LockThread thread = new LockThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread.leftRight();
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread.rightLeft();
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
结果为:
left...start
right...start
right...a=false
right end!
left...a=false
left end!
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at cn.thread.first.dielock.LockThread.rightLeft(DeadLock3.java:35)
at cn.thread.first.dielock.DeadLock3$2.run(DeadLock3.java:61)
at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at cn.thread.first.dielock.LockThread.leftRight(DeadLock3.java:21)
at cn.thread.first.dielock.DeadLock3$1.run(DeadLock3.java:50)
at java.lang.Thread.run(Thread.java:745)
可以看到tryLock在尝试过程中没有获取到锁,就不加锁,所以在finally里面,解锁就会抛异常了。
tryLock只是尝试看看自己能否获取锁,能获取就获取锁并返回true,不能获取就直接返回false不做任何操作,没有获取锁的时候并不会把线程加入到等待队列里面(相当于没有锁,不加锁),tryLock只是在能获取锁的时候获取锁。
在多把锁为防止死锁可以尝试使用tryLock来操作,或者使用boolean a = lock2.tryLock(1, TimeUnit.SECONDS);在1s钟内一直尝试获取锁,如果1s后还不能获取则返回false。这样就可以很好的解决死锁带来的问题。
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ProducerAndCosumer {
private static final Integer MAX_ITEMS = 10;
private LinkedList items = new LinkedList<>();
private Lock lock = new ReentrantLock();
private Condition full = lock.newCondition(); //元素满的时候,生产者进入等候
private Condition empty = lock.newCondition(); //元素为空的时候,消费者进入等候
public T put(T o) throws InterruptedException {
lock.lock();
try {
while (items.size() == MAX_ITEMS) {
full.await();
}
insert(o);
} finally {
lock.unlock();
}
return o;
}
private void insert(T o) {
items.add(o);
empty.signal(); //解锁消费者等候
}
public T get() throws InterruptedException {
lock.lock();
try {
if (items.isEmpty()) {
empty.await();
}
full.signal(); //解锁生产者等候
return items.removeFirst();
} finally {
lock.unlock();
}
}
}
public class ProducerAndCosumerDemo {
public static void main(String[] args) {
final ProducerAndCosumer cosumer = new ProducerAndCosumer<>();
Runnable producerRunnable = new Runnable() {
int i = 0;
public void run() {
while (true) {
i++;
try {
System.out.println("我生产了一个===" + i);
cosumer.put(i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Runnable cosumerRunnable = new Runnable() {
public void run() {
while (true) {
try {
System.out.println("消费了一个===" + cosumer.get());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(producerRunnable);
thread.start();
Thread thread1 = new Thread(cosumerRunnable);
thread1.start();
}
}
队列满了,那么生产者就开始等候;队列空了,消费着就开始等候;队列有值了,就解锁消费者等候;消费掉一条数据时就解锁生产者等候。
可以参考ArrayBlockingQueue源码实现。
volatile赋予了变量可见——禁止编译器对成员变量进行优化,它修饰的成员变量在每次被线程访问时,都强迫从内存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性
可参看《java多线程-volatile》与《java多线程-synchrionized》