java多线程读书笔记
1. 线程的状态:新生(New),可运行(Runable),被阻塞(Blocked),死亡(Dead)。
2. 线程的优先级
Java 中线程一共有十个优先级1-10。Java线程的优先级依赖于宿主计算机的线程实现机制。
//设置优先级的方法 thread.setPriority(); //三个默认的优先级 MIN_PRIORITY //1 NORM_PRIORITY //5 MAX_PRIORITY //10
|
3. 守护线程
守护线程的作用就是为其他线程提供服务,如果程序中只剩下守护线程则程序即将退出。
// public class Counter implements Runnable {
@Override public void run() { try { while (!Thread.currentThread().isInterrupted()) { Thread.sleep(100); System.out.println("当前时间为:" + new Date()); } } catch (InterruptedException e) { System.out.println("线程中断"); Thread.currentThread().interrupt(); } }
} |
public class DemonThread { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Counter()); //注意要给线程命名,排错方便 t.setName("Counter"); t.setDaemon(true); //设置此线程为守护线程 t.start(); TimeUnit.SECONDS.sleep(5); System.exit(0); } }
|
4. 线程组
线程组表示一组线程的集合,线程组中仍然可以包含其他的线程。
//简单的实例 package org.wk.core.thread;
public class ThreadInGroup { private static int counter = 0;
public static void main(String[] args) { ThreadGroup tg = new ThreadGroup("Threads");
Thread t1 = new Thread(tg, new Runnable() { @Override public void run() { try { while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "->" + counter++); Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被中断!"); Thread.currentThread().interrupt(); } } }, "counter1"); //t1.start();
Thread t2 = new Thread(tg, new Runnable() { @Override public void run() { try { while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "->" + counter++); Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被中断!"); Thread.currentThread().interrupt(); } } }, "counter2"); t2.start();
System.out.println("线程组的名称:" + t1.getThreadGroup().getName()); System.out.println("线程组的名称:" + t2.getThreadGroup().getName()); System.out.println("线程组中活越线程数量:" + tg.activeCount()); } }
|
5. 未捕获异常处理器
非受检异常(UncheckedException)可能会意外地导致线程终止,这就需要一种异常处理器进行处理。用户可以自定义处理器只需要继承Thread.UncaughtException。
//Exception Handler package org.wk.core.thread;
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName() + "发生异常!"); e.printStackTrace(); } }
// package org.wk.core.thread;
import java.util.ArrayList; import java.util.List;
public class TestUncaughtExceptionHandler { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { @Override public void run() { while (true) { List<String> list = new ArrayList<String>(); System.out.println(list.get(0)); } } }); // 注意要给线程命名,排错方便 t.setName("Counter"); t.setUncaughtExceptionHandler(new ExceptionHandler()); t.start(); } } |
6. Lock(锁)机制
(1)使用锁时一定要注意释放
lock.lock(); try { //do something ... ... } finally { lock.unclock(); }
|
(2)锁是可重入的
但是内部锁是可重入的,当线程试图获得它自己占有的锁时,请求总会成功。锁对象维护着一个持有计数(hold count)来追踪对lock方法的嵌套调用。从这个特性我们可以知道被一个锁保护的方法可以调用另一个使用同一个锁的方法。
package org.wk.core.thread;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLocks { private Lock lock = new ReentrantLock();
public synchronized void print() { System.out.println("in method print()!"); print(); // 由于锁的可重入性,程序总会进入print()方法,总是持有这个锁不会释放,知道内存溢出。 }
public void write() { lock.lock(); try { System.out.println("int method write()!"); write(); // 由于锁的可重入性,程序在此会发生死锁 } finally { lock.unlock(); } }
public static void main(String[] args) { ReentrantLocks rl = new ReentrantLocks(); rl.print(); rl.write(); } }
|
7. 条件对象(又称条件变量condition variable)---Condition
假设线程进入临界区,但是程序不满足运行的条件。条件对象就是用来管理那些获得了锁但是不能执行的线程。调用了await()方法的线程会进入这个等待该条件集中,当锁可获得时线程不能立即解除阻塞状态,直到一个线程调用了这个条件上的signal/signallAll()方法。当条件满足时会随机选取组则队列中的线程进行恢复。
package org.wk.core.thread;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100]; int putptr, takeptr, count;
public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } }
public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
|
何时调用signal()/signallAll()方法呢?原则是当对象的状态向着有利于解除等待线程的方向变化是调用该方法。
9. Synchronized关键字
(1)使用synchronized并不是锁对象没有了锁,只是将显示的锁变成了隐式的锁,并且有一个隐式的条件。由锁管理进入synchronized的线程,由隐式的条件管理哪些需要调用wait()的线程。
(2)隐式锁和条件的缺点:不能中断一个正在试图获得锁的线程;试图获得锁时不能设定超时。
(3)synchronized是Lock的一种简化实现。一个Lock可以对应多个Condition,而synchronized只能对应一个Condition。
10. 监视器
11. 同步块
12. Volatile:声明为volatile的变量,程序每次都去内存中读取它的值,而不去缓冲区中或者寄存器中读取它的值。
a) 三种并发访问安全的域:final关键字修饰的;volatile修饰的;由锁保护的。
13. 死锁
死锁产生的原因:
举一个简单的例子:张三、李四两个人排队去厕所。完成去厕所的过程需要两个必要条件:号牌和卫生纸。
由于张三比李四来的时间稍早一些他领到了号牌,但是张三有一个毛病-健忘、一根筋,他忘了带卫生纸。所以张三不能完成去厕所的动作,焦急的等在厕所门口。李四倒是一个细心地人,带了足够的卫生纸,但是由于张三李四平时"有矛盾"所以李四不可能借卫生纸给张三,李四也焦急的等在厕所门口。这样张三李四被拒之厕所门外。
从上面的故事中看出:a.卫生纸和号牌是临界资源;b.张三,李四是两个被阻塞的线程;c.厕所相当于cpu。
解决上述问题的方案:a. 张三把号牌让给李四;b. 李四送给张三卫生纸。
14. 读写锁
Synchronized(this) { ... ... } |
1. 运行stop()方法会停止所有未结束的方法(包括run()方法),当一个线程停止时会立即释放锁,这回导致对象的不一致状态。
2. 停用suspand()方法
If the thread that would resume the target thread attempts to lock this monitor prior to calling resume, deadlock results.
如果将恢复目标线程的线程试图在调用resume之前锁定该监视器。
3. 如何停止一个线程without method stop
Private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); Private Lock readLock = lock.readLock(); Private Lock writeLock = lock.writeLock(); //对指定代码添加读锁和写锁和普通的锁并无二样 |
private volatile Thread blinker;
public void stop() { blinker = null; }
public void run() { Thread thisThread = Thread.currentThread(); while (blinker == thisThread) { try { thisThread.sleep(interval); } catch (InterruptedException e){ } repaint(); } }
|
对于一个可能长时间等待的线程我们如何处理
4. 替代suspand()
public void stop() { Thread moribund = waiter; waiter = null; moribund.interrupt(); }
|
public synchronized void mousePressed(MouseEvent e) { e.consume();
threadSuspended = !threadSuspended;
if (!threadSuspended) notify(); }
and adding the following code to the "run loop": synchronized(this) { while (threadSuspended) wait(); }
|
private boolean volatile threadSuspended;
public void run() { while (true) { try { Thread.currentThread().sleep(interval);
if (threadSuspended) { synchronized(this) { while (threadSuspended) wait(); } } } catch (InterruptedException e){ } repaint(); } }
|
两者合用(这时候要注意线程的状态,当需要线程停止时,需要改变线程的挂起状态。)
public void run() { Thread thisThread = Thread.currentThread(); while (blinker == thisThread) { try { thisThread.sleep(interval);
synchronized(this) { while (threadSuspended && blinker==thisThread) wait(); } } catch (InterruptedException e){ } repaint(); } }
public synchronized void stop() { blinker = null; notify(); }
|
16. 阻塞队列
LinkedBlockingQueue;ArrayBlockingQueue;PriorityBlockingQueue;DelayQueue;SynchronousQueue。
使用阻塞队列我们要注意:尽量不要使用从Queue继承的方法,这样就失去了Blocking的特性了。
模拟阻塞队列:
package org.wk.core.thread;
import java.util.LinkedList;
public class ImitateBlockingQueue<T> { private Object notEmpty = new Object(); private LinkedList<T> list = new LinkedList<T>();
// 添加 public void offer(T element) throws InterruptedException { synchronized (notEmpty) { if (list.size() == 0) { notEmpty.notifyAll();//执行notify(),notiyAll()操作以前必须首先获得锁 } list.add(element); } }
// 删除 public T take() throws InterruptedException { synchronized (notEmpty) { if (list.size() == 0) { notEmpty.wait();//执行wait()操作以前必须获得锁。 } return list.poll(); } } }
|
package org.wk.core.thread;
import java.util.LinkedList;
public class ImitateBlockingQueue2<T> { private Object notEmpty = new Object(); private Object notFull = new Object(); private LinkedList<T> list = new LinkedList<T>(); private final int CAPACITY = 200;
// 添加 public void offer(T element) throws InterruptedException { synchronized (notEmpty) { if (list.size() == 0) { notEmpty.notifyAll(); } synchronized(notFull) { if (list.size() == CAPACITY) { notFull.wait(); } list.add(element); } } }
// 删除 public T take() throws InterruptedException { //一个synchronized只能对应一个Condition synchronized (notEmpty) { if (list.size() == 0) { notEmpty.wait(); }
synchronized(notFull) { if(list.size() == CAPACITY) { notFull.notifyAll(); } return list.poll(); }
} } }
|
package org.wk.core.thread;
import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class ImitateBlockingQueue3<T> { private Lock lock = new ReentrantLock(); private Condition notEmpty = lock.newCondition(); private Condition notFull = lock.newCondition(); private LinkedList<T> list = new LinkedList<T>(); private final int CAPACITY = 200;
// 添加 public void offer(T element) throws InterruptedException { lock.lock(); try { //如果仓储为空,唤醒所有的生产者 if (list.size() == 0) { notEmpty.notifyAll(); } //如果仓储已满,挂起所有的生产者 if (list.size() == CAPACITY) { notFull.wait(); } list.add(element); } finally { lock.unlock(); } }
// 删除 public T take() throws InterruptedException { lock.lock(); try { //如果仓储为空,挂起所有的消费者 if (list.size() == 0) { notEmpty.wait();// 执行wait()操作以前必须获得锁。 } //如果仓储已满,唤醒所有的消费者 if (list.size() == CAPACITY) { notFull.notifyAll(); } return list.poll(); } finally { lock.unlock(); } } }
|
17. Java中提供的原子操作类
AtomicInteger,AtomicBoolean,AtomicLong,AtomicReference等。这是由硬件提供原子操作指令实现的。在非激烈竞争的情况下,开销更小,速度更快。
18. Lock-free算法。
算法简介:运用线程安全集合类,或者基本类型包装类的并发特性进行无锁编程。
LockFree算法,不需要加锁。通常都是三个部分组成: ①循环 ②CAS (CompareAndSet) ③回退
|
class CounterWithLock { private volatile int counter = 0;
public synchronized void set(int value) { if (value > counter) { counter = value; } }
public int get() { return counter; } }
class CounterWithoutLock { private AtomicInteger counter = new AtomicInteger(0);
public void set(int value) { for (;;) { int temp = counter.get(); if (value > temp) { if (counter.compareAndSet(temp, value)) { break; } else { continue; } } else { break; } } }
} |
19. 线程安全的集合
ConcurrentLinkedQueue;ConcurrentHashMap
Vector;HashTable
CopyOnWriteArrayList;CopyOnWriteArraySet
JAVA API如是形容CopyOnWriteArayList:ArrayList 的一个线程安全的变体,其中所有可变操作(添加、设置,等等)都是通过对基础数组进行一次新的复制来实现的。这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更 有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内绝不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。自创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。不支持迭代器上更改元素的操作(移除、设置和添加)。这些方法将抛出 UnsupportedOperationException。
20. 锁的使用规则:
a) 使用支持CAS的数据结构,避免使用锁,如:AtomicXXX、ConcurrentMap、CopyOnWriteList、ConcurrentLinkedQueue
b) 一定要使用锁的时候,注意获得锁的顺序,相反顺序获得锁,就容易产生死锁。
c) 死锁经常是无法完全避免的,鸵鸟策略被很多基础框架所采用。
d) 外部锁常被忽视而导致死锁,例如数据库的锁
19. Callable和Future
package org.wk.core.thread;
import java.util.Date; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException;
class Computer implements Callable<Date> { @Override public Date call() throws Exception { return new Date(); } }
public class GetResult { public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { FutureTask<Date> task = new FutureTask<Date>(new Computer()); Thread t = new Thread(task, "computer"); t.start(); Date d = task.get(3, TimeUnit.SECONDS); System.out.println(d);
} } |
提供一个java并行计算的框架
20. 同步器
Cyclicbarrier;CountDownLatch;Exchanger;SychhronousQueue;Semaphore。
CountDownLatch的用法举例
http://www.cnblogs.com/focusj/archive/2012/01/05/2313761.html |
package org.wk.core.thread;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;
class Worker implements Runnable { private CountDownLatch latch; private String name;
public Worker(CountDownLatch latch, String name) { this.latch = latch; this.name = name; }
@Override public void run() { System.out.println(name + "is doing work!"); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } this.latch.countDown(); } }
class Boss implements Runnable { private CountDownLatch latch; private String name;
public Boss(CountDownLatch latch, String name) { this.latch = latch; this.name = name; }
@Override public void run() { System.out.println(name + "is checking work!"); try { this.latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class TestCountDownLatch { public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(10); CountDownLatch latch = new CountDownLatch(3); Worker w1 = new Worker(latch, "worker1"); Worker w2 = new Worker(latch, "worker2"); Worker w3 = new Worker(latch, "worker3"); Boss boss = new Boss(latch, "boss");
exec.submit(w1); exec.submit(w2); exec.submit(w3); exec.submit(boss);
exec.shutdown(); } }
|
CyclicBarrier用法
package org.wk.core.thread;
import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;
class Slaver implements Runnable { private CyclicBarrier barrier; private String name;
public Slaver(CyclicBarrier barrier, String name) { this.barrier = barrier; this.name = name; }
@Override public void run() { try { TimeUnit.SECONDS.sleep(new Random(47).nextInt()); this.barrier.await(); System.out.println(name + "is doing work!"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }
public class TestCyclicBarrier { public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(10); CyclicBarrier barrier = new CyclicBarrier(3); Slaver w1 = new Slaver(barrier, "slaver1"); Slaver w2 = new Slaver(barrier, "slaver1"); Slaver w3 = new Slaver(barrier, "slaver1");
exec.submit(w1); exec.submit(w2); exec.submit(w3);
exec.shutdown(); } }
|