Java-ConCurrent2.html
ConCurrent in Practice小记 (2)
Java7 Concurrency Cookbook
裸线程控制(Thread Control)
Thread Interrupt
Thread的interrupt()方法使得线程中断,而检测Thread中断有两个方法,isInterrupt()和interrupted(),这里的interrupted()方法是静态方法,并且在得到interrupt属性值后会将其再置为false,但是isInterrupted仅仅返回属性值。
一般建议使用isInterrupted()
当Thread处于sleep状态的时候,CPU系统会离开该任务而去执行其他的任务,但是一旦interrupt指令出现,则无论Thread在什么状态,立即响应interrupt。
在API中也可以调用yield()来使得CPU离开当前线程的任务去执行其他的任务,JVM会保证任务的执行。
Thread的汇合控制
对裸的Thread汇合控制,即等待某些线程的结束进行下一个线程内容的执行,可以用join控制。用法是在当前线程A中调用另一线程B的join方法,会让线程A等待B的执行完成,然后再执行线程A后的代码。
守护线程(Daemon Thread)
守护线程是Java中一种特殊的线程,该线程的优先级特别低,通常是在系统没有任务执行时才去执行的线程;并且该类线程在JVM中,当其他正常线程都结束时,此时无论守护线程是否还在工作,是否还存在都会使得JVM退出。
所以该类线程通常是给别的线程提供服务的,该类线程最知名的代表是GC(Garbage Collection)。
在线程初始化后可以将线程设置为Daemon,但是一旦start就不能再改变线程的属性。
Thread 未检查异常
Thread中出现UncaughtException,JVM会寻找3中handler来处理;
- 会在当前线程的对象中寻找handle,
- 当前线程对象中找不到就到线程对象的ThreadGroup中找
- 如果扔不存在,JVM则会寻找默认非捕捉异常的handle。
最后,如果异常仍未被处理,JVM则会终止程序,打印异常到控制台。
本地线程变量
本地线程变量要解决的问题是当多个线程可以访问同一个对象的变量(非静态)时,任意一个线程对该对象的属性的改变都会体现在其他线程中。(本质是后一次线程对之前线程中对对象的数据(属性)的修改会覆盖。)
要解决这个问题,就是使用本地线程变量,每个线程维护自己的变量,对其他线程不可见。
这里先记要点,具体的补充等以后再说,要点:
- 内部维护一个ThreadlocalMap来储存每一个Thread访问对象,并将其作为key值,内部存储的副本作为value。
- 共有四个方法,get,set,initialValue,remove
- JDK1.5后都使用泛型
- 不知道为什么一般都声明为static,甚至还有final的。
还包括子类InheritableThreadLocal,即在里面线程里面再开线程得到的线程中本身就有母线程的本地变量,应该也是维护自己的本地变量。
线程组ThreadCroup
线程组应用的场合主要在于多条线程完成同一份工作,一般只要有一个得到结果就可以结束整个任务过程。
线程工厂
设计模式中的工厂模式,产生一系列新的对象而已。也可以自己实现,ThreadFactory接口中仅仅有一个newThread方法需要实现。
同步问题
同步是解决当不同的线程访问共享的资源时,出现的数据不一致问题。
临界区:包含有共享资源,同一时刻不能有多个线程访问的代码块。 (特别地:由于静态方法同对象方法是不同的存储空间和方式,因此即使对一个对象加锁,但是仍然会出现一个线程访问静态方法,而另一个方法访问该对象的对象方法的情况。此时如果两个方法影响同一个对象的变量,还是会出现同步问题。)
Synchronized关键词
Synchronized是Java并发中最古老的同步语句, 强调!!:永远是对对象加锁!!,所有的方法,即属性加锁,本质上都是对对象的锁。正是Java每个对象的内部锁机制,使得当不同的线程访问同一对象时,才控制对属性的访问权限。
Tips:
由于竞争中的线程拿不到资源就会等待,因此应该尽量减少synchronized关键字的作用域,所以有时候使用代码快的同步:并且有可能直接拿一个object作为同步锁标志代替this。一般是在独立属性的访问中使用非this的方式。
synchronized(this){
//JavaCode
}
Synchronized是可重入的,因此得到锁的线程可以递归调用synchronized方法。
同步中使用条件
基本的条件语句就是wait(),notify(),notifyAll().
同步中使用条件的最典型应用就是简单的生产消费者模型,两边维护一个队列,当达到一定的长度的时候就阻塞,阻塞使用wait()让生产者停止加入队列,同时notify()通知其他线程的消费者来取;同样地,当消费者取到了队列的尾部,同样自己wait(),并通知其他线程的生产者进行生产notify().
Lock
Synchronized的缺陷:
由于Synchronized是依据对象的内部锁来进行对线程访问加锁的,因此会出现一下缺陷。
- 当某一线程已经获得锁时,其他线程只能等待,如果此线程中有耗时操作或者等待,就只能持续下去,不能打断。仅当其释放锁或者发生异常才能得到锁。
- 针对读写操作,多个读写线程中,读写和写写线程之间应当加锁,但是读读线程之间不需要,synchronized做不到。
所以在Java1.5之后引入了一个Lock的类,不同于原生的synchronized,用于实现Lock的功能。
Lock类的使用:
如下面的示例
public class Test {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
public static void main(String[] args) {
final Test test = new Test();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
}
public void insert(Thread thread) {
Lock lock = new ReentrantLock(); //注意这个地方
lock.lock();
try {
System.out.println(thread.getName()+"得到了锁");
for(int i=0;i<5;i++) {
arrayList.add(i);
}
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()+"释放了锁");
lock.unlock();
}
}
}
这里,lock是在一个方法中的局部变量,因此在多个线程访问时,会每一个线程保存一个lock的副本,实际上并没有起到同步的作用。
正确的做法应该是将lock作为类的独立属性来对待。
tryLock的使用:
if(lock.tryLock()) {
try {
System.out.println(thread.getName()+"得到了锁");
for(int i=0;i<5;i++) {
arrayList.add(i);
}
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()+"释放了锁");
lock.unlock();
}
} else {
System.out.println(thread.getName()+"获取锁失败");
}
记住:!!!永远在finally中手动释放锁,否则会引发死锁。
lockInterruptly()的用法:
public class Test {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Test test = new Test();
MyThread thread1 = new MyThread(test);
MyThread thread2 = new MyThread(test);
thread1.start();
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.interrupt();
}
public void insert(Thread thread) throws InterruptedException{
lock.lockInterruptibly(); //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
try {
System.out.println(thread.getName()+"得到了锁");
long startTime = System.currentTimeMillis();
for( ; ;) {
if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
break;
//插入数据
}
}
finally {
System.out.println(Thread.currentThread().getName()+"执行finally");
lock.unlock();
System.out.println(thread.getName()+"释放了锁");
}
}
}
class MyThread extends Thread {
private Test test = null;
public MyThread(Test test) {
this.test = test;
}
@Override
public void run() {
try {
test.insert(Thread.currentThread());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"被中断");
}
}
}
记得抛出异常。这里等待得到锁的线程可以被打断。
ReadWriteLock的使用:
ReentrantReadWriteLock是ReadWriteLock的唯一实现,目的是实现在读写过程中的接口分离,这里的lock对象具有两个锁,一个readLock,一个writeLock,两者互不干扰。
用法:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock.lock();
//Java code
lock.readLock.unlock();
.............
lock.writeLock.lock();
//Java code
lock.writeLock.unlock();
总结:
- synchronized是语言的内置机制,而lock只是一个普通类。
- lock的使用中必须记得释放锁,synchronized本身出现异常时,JVM会将异常抛出且释放锁,但是lock包不会,因此必须将unlock()写在finaly中。
- lock可以使用tryLock()看是否能得到锁,然后立即返回,不需要一直等待。
- lock可以使用中断等待锁的机制。
- lock可以实现多路读写的接口分离。
- 性能上lock略高。
公平锁
在初始化锁时,增加一个boolean值,是否启用公平锁,启用之后就会使得等待时间最长的线程优先得到锁。而不是多个线程去竞争。
在Lock中使用Condition条件
Condition类是取代之前和synchronized配合的条件类型的,如wait(),notifyAll()。所有的Condition都依赖于lock类,通过lock类中的newCondition()方法得到。对Condition的使用都在lock的lock()和unlock()作用域之间。
Condition的await()用来取代原来的wait(),signal(),signalAll()分别代替原来的notify().notifyAll()。
特别地:注意!当调用await(),对象释放锁,另一个线程可以得到执行。但是对于调用了signalAll()之后,其他相关的线程都会被唤醒,而当某一个线程开始执行,并不能保证其余的线程依然睡眠,因此应该将await()放入while中。原文如下:
When a thread calls the signal() or signallAll() methods of a condition, one or all of the threads that were waiting for that condition are woken up, but this doesn’t guarantee that the condition that made them sleep is now true, so you must put the await() calls inside a while loop.You can’t leave that loop until the condition is true. While the condition is false, you must call await() again.
如果在某个condition上调用了await(),但是没有调用signal(),那么该线程会永远沉睡。
另外:await()的其他版本:
await(long time, TimeUnit unit) 该对象会被以下条件唤醒,(1)时间到,(2)被中断,(3)被signal()唤醒。
awaitUninterruptibly(): 该线程不会被打断,一直到有signal()出现。
awaitUntil(Date date): 类似上面的有时间的版本。
读写锁也可以和condition一起使用。
generated by haroopad