并发编程总结五
1、现在有线程T1、T2和T3。你如何确保T2线程在T1之后执行,并且T3线程在T2之后执行?
这个问题主要是考察 join( ) 方法的应用,如果一个线程A执行了 thread.join() 语句,其含义是:当前线程A等待 thread 线程终止之后才从 thread.join() 返回。也就是说,当线程A执行了线程thread的 join 方法时,会先执行线程thread中的内容,等待从join方法返回之后,才会继续执行线程A方法剩下的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class JoinLearn{ static class MyThread implements Runnable{ private Thread thread; public MyThread(Thread thread){ this.thread = thread; }
@Override public void run() { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在运行......"); } } public static void main(String[] args){ Thread previous = Thread.currentThread(); Thread t1 = new Thread(new MyThread(previous), String.valueOf(1)); t1.start(); Thread t2 = new Thread(new MyThread(t1), String.valueOf(2)); t2.start(); Thread t3 = new Thread(new MyThread(t2), String.valueOf(3)); t3.start(); } }
|
2、Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?
- Lock接口提供了与synchronized类似的同步功能,只是在使用时需要显示地获取锁和释放锁
- 拥有了获取锁与释放锁的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。Lock多了锁投票,定时锁等候和中断锁等候等特性。
有这么一种情况,线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定。如果使用synchronized ,如果A不释放,B将会一直等待下去,而且B的等待不可以被中断。如果使用了 ReentrantLock ,如果A不释放,可以使B在等待了足够长的时间以后中断等待,而干别的事情。
接下来我们来说一下两者的用法:
- synchronized 可以锁定一个方法,也可以锁定一个对象,synchronized不需要自己定义获取锁和释放锁,这些都是隐式的。一旦线程A获取到锁之后,就只有线程A可以访问synchronized修饰的这个部分,只有等到线程A释放锁之后,其他线程才有访问的机会。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Sync implements Runnable{ private Data data = new Data();
@Override public synchronized void run() { int tmp = data.getmVal(); ++tmp; data.setmVal(tmp); System.out.println(Thread.currentThread().getName()+"|"+data.getmVal()); }
public static void main(String[] args) { Sync myThread = new Sync(); for (int i = 0; i < 5; i++) { Thread thread = new Thread(myThread); thread.start(); } } }
|
运行结果为:
1 2 3 4 5
| Thread-0|1 Thread-1|2 Thread-2|3 Thread-3|4 Thread-4|5
|
如果不使用synchronized修饰时,可能会产生如下结果:
1 2 3 4 5
| Thread-0|2 Thread-1|2 Thread-2|3 Thread-4|4 Thread-3|5
|
- Lock 由于需要自己手动获取锁和释放锁,当代码出现异常时,有可能因为下面的代码执行不到导致锁无法释放,因此要把释放锁放在finally中,我们同样适用上面synchronized的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class LockLearn implements Runnable{ Lock lock = new ReentrantLock(); Data data = new Data();
@Override public void run() { lock.lock(); try { int tmp = data.getmVal(); ++tmp; data.setmVal(tmp); System.out.println(Thread.currentThread().getName()+"|"+tmp); }finally { lock.unlock(); } }
public static void main(String[] args) { LockLearn lock = new LockLearn(); for (int i = 0; i < 5; i++) { Thread thread = new Thread(lock); thread.start(); } } }
|
3、Java 中 wait 和 sleep 方法有什么区别?
根据我的理解以及查阅其它博客,大致总结一下 wait 和 sleep 的区别:
sleep
- 让当前线程进入停滞状态(即阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会。
- 在一个synchronized块中调用 sleep 方法时,线程虽然休眠但是不会释放锁。
- 在sleep 休眠时间满之后,该线程并不一定会立即执行,这是因为其他线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
wait
- wait 方法不是线程中的方法,这个方法属于 Object 类;当一个线程执行 wait 方法时,它就进入到一个和该对象相关的等待池中,同时会释放锁。
- wait 使用 notify 或者 notifyAll 或者指定的睡眠时间来唤醒当前等待池中的线程
- wait 必须放在 synchronized block 中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。
4、如何在 Java 中实现一个阻塞队列?
阻塞队列和普通队列的区别就是,当队列中没有元素的时候,调用出队方法的线程将会进入等待状态,直到队列中存在元素时线程会再次被唤醒;当队列已满的时候,调用入队方法的线程将会进入等待状态,直到有线程从队列中出队时该线程才会被唤醒。
我们使用 wait 和 notifyAll 方法来实现一个基本的阻塞队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class BlockingQueue { private List queue = new LinkedList();
private int limit = 10; public BlockingQueue(int limit){ this.limit = limit; }
public synchronized void enqueue(Object item) throws InterruptedException { while (this.queue.size() == limit){ wait(); } if (this.queue.size() == 0){ notifyAll(); } this.queue.add(item); }
public synchronized Object dequeue() throws InterruptedException { while (this.queue.size() == 0){ wait(); } if (this.queue.size() == this.limit){ notifyAll(); }
return this.queue.remove(0); } }
|
5、写一段死锁代码。
说来就来,上死锁代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class DeadLockDemo { private static String A = "A"; private static String B = "B";
public static void main(String[] args) { new DeadLockDemo().deadLock(); }
private void deadLock(){ Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized (A){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (B){ System.out.println("1"); } } } });
Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized (B){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (A){ System.out.println("2"); } } } });
t1.start(); t2.start(); } }
|
从代码上可以看出,线程t1拿到了代码块A的锁,t2拿到了代码块B的锁,后来t1想拿到B的锁,t2想拿到A的锁,但是两条线程都不会释放锁,所以两者都陷入了等待状态,变成了死锁。
6、 既然 start() 方法会调用 run() 方法,为什么我们调用 start() 方法,而不直接调用 run() 方法?
start( ):他的作用是启动一个新线程,新线程会执行响应的 run( ) 方法,start( ) 不能被重复调用
run( ): run( ) 就和普通的成员方法一样,可以被重复调用。单独调用run( )的话,会在当前线程中执行run( ),而不会启动新的线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class RunAndStartDemo implements Runnable{
@Override public void run() { System.out.println(Thread.currentThread().getName()+" is running"); }
public static void main(String[] args) { RunAndStartDemo demo = new RunAndStartDemo();
Thread thread = new Thread(demo); System.out.println("调用run()方法..."); thread.run(); System.out.println("调用start()方法..."); thread.start(); } }
|
运行结果为:
1 2 3 4
| 调用 run() 方法... main is running 调用 start() 方法... Thread-0 is running
|
8. Java 中 volatile 关键字是什么?你如何使用它?它和 Java 中的同步方法有什么区别?
自从 Java 5 中调整 volatile 关键字和 Java 内存模型后,有关 volatile 关键字的线程问题越来越常见。掌握 volatile变量在并发环境中如何确保可见性、有序性和一致性非常重要。