《Java编程思想》整理的一些学习笔记。有不对的地方。欢迎指出。
1 .资源冲突,假设两个线程确实是在改动同一个对象,共享资源的冲突将变得更糟糕。由于这有可能把对象设置成不对的状态。通过简单的“信号量”概念引入,把它看作是在两个线程之间进行通信的标志对象。假设信号量的值是零。则它监控的资源是可用的,但假设这个值是非零的,则被监控的资源不可用,所以线程必须等待。
当资源可用的时候,线程添加信号量的值,然后继续运行这个被监控的资源。把添加和降低信号量的操作定义为原子操作,这样就可保证两个线程同一时候訪问同一资源的时候不至于冲突。
定义一个简化的信号量:
public class Semaphore implements Invariant{
private volatile int semaphore = 0;
public boolean available(){return semaphore==0;}
public void acquire(){ ++semaphore; }
public void release(){ --semaphore; }
public InvariantSate invariant(){
int val = semaphore;
if( val==0||val==1 )
return new InvariantOk();
else
return new InvariantFailure(new Integer(val));
}
}
(当中Invariant接口在博客:线程測试框架已给出)将semaphore字段设置为volatile 。以确保编译器不会对不论什么读取此值的操作进行优化。
2.解决共享资源竞争。之前说过,能够通过yield()和setPriority()来给线程调度机制提供建议。但这些建议未必会有多大的效果。这取决与你的详细平台和JVM实现。
Java以提供关键字 synchronized 的形式,为防止资源冲突提供了内置支持。
共享资源通常是以对象的形式存在的内存推断,但也能够是文件。输入/输出port。或者是打印机。
要控制对共享资源的訪问。得先把它包装进一个对象。然后把全部要訪问这个资源的方法标记为synchronized。即一旦某个线程处于一个标记为synchronized的方法中,那么在这个线程从该方法返回之前,其它全部要调用类中不论什么标记为synchronized方法的线程都会被堵塞。
每一个对象都含有单一的锁(也称为监视器),这个锁本身就是对象的一部分(不用写不论什么特殊代码)。当在对象上调用其随意synchronized方法的时候,此对象都被加锁,这时该对象上的其它synchronized方法也仅仅能等到前一个方法调用完并释放了锁之后才干被调用。
针对每一个类也有一个锁(作为类的Class对象的一部分)。所以synchronized static 方法能够在类的范围内防止对static数据的并发訪问。
3.原子操作,即不能被线程调度机制中断的操作;一旦操作開始。那么它一定能够在可能发生的“上下文切换”之前(切换到其它线程运行)运行完毕。假设问题中的变量类型是除long或double以外的基本类型。对这样的变量进行简单的赋值或返回值操作的时候,才算是原子操作。
然而,仅仅要给long或double加上volatile,操作就是原子的了。注意,在JVM中的自添加操作并非原子操作。它牵涉到一次读和一次写,所以即使在这样的简单操作中,也为线程出问题提供了空间。线程工作时。每一个线程都可能拥有一个本地栈来维护一些变量的复本,假设把一个变量定义成volatile的。就等于告诉编译器不要做不论什么优化,直接在主存操作变量。
4.保证上述问题解决。做安全的做法就是使用以下的方法:
1)假设要对类中的某个方法进行同步控制,最好同步全部方法。
假设忽略了当中一个。通常非常难确定这么做是否会有负面影响。
2)当去除方法的同步控制时。要非常小心。通常这么做是基于性能方面的考虑,但在JDK1.3和JDK1.4中,同步控制所需的负担已经大大的降低。
此外。仅仅应在使用性能评价工具证实了同步控制确实是性能瓶颈的时候。才这么做。
5.假设仅仅是希望防止多个线程同一时候訪问方法内部的部分代码而不是防止整个方法。能够使用synchronized关键字来分离代码段。这样的方式被称为“临界区”,此时,synchronized被用来指定某个对象。此对象的锁被用来对花括号内的代码进行同步控制:
synchronized(syncObject){
// This code can be accessed
//by only one thread at a time
}
使用同步控制块。而不是对整个方法进行同步控制,能够使多个线程訪问对象的时间性能得到显著的提高。要注意的是,当对象中的方法在不同的锁上同步的时候,两个线程能够訪问同一个对象:
class DualSynch {
private Object syncObject = new Object();
public synchronized void f() {
System.out.println("Inside f()");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("leaving f()");
}
public void g() {
synchronized (syncObject) {
System.out.println("Inside g()");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("leaving g()");
}
}
}
public class SyncObject{
public static void main(String[] args){
final DualSynch ds = new DualSynch();
new Thread(){
public void run(){
ds.f();
}
}.start();;
ds.g();
}
}
DualSync对象的f()方法在this上同步(通过在整个方法同步),g()的同步控制块在syncObject对象上同步。因此,两个同步控制相互独立,两个方法同一时候鱼腥,所以它们没有在对象的同步控制上堵塞。因此。必须把訪问共享资源的代码段包装进一个合适的同步控制块。
6.线程有四个状态:新建、就绪、死亡、堵塞(程序能够运行,但有某个条件阻止它运行)。进入堵塞状态的原因:
1)通过调用sleep(miliseconds)使线程进入休眠状态,在指定的时间内不运行。
2)调用wait()使线程挂起,直到线程得道了notify()或notifyAll()消息,线程才会进入就绪状态。
3)线程在等待某个输入/输出完毕。
4)线程在某个对象上调用其同步方法,可是对象锁不可用。
7.线程之间为避免冲突,通过“握手机制”来进行的。这样的握手能够通过Object的方法wait()和notify()来安全的实现。注意。调用sleep()的时候锁并没有被释放,而调用wait()方法的确释放了锁。这就意味着。再调用wait()期间,能够调用线程对象中的其它同步控制方法,当一个线程在方法里遇到了对wait()的调用的时候,线程的运行被挂起,对象上的锁被释放。
wait()有两种形式。一种与sleep()一样接受毫秒数,不同之处:
1)在wait()期间对象锁是释放的。
2)能够通过notify()、notifyAll()。或者指令时间到期。从wait()中回复运行。
还有一种是不带參数的,wait()将无限等下去。知道接收到notify()或notifyAll()的消息。
8.wait()、notify()、notifyAll()这些方法是基类Object的一部分,而不是像sleep()那样属于Thread的一部分。由于这些功能要用到的锁也是全部对象的一部分,所以。你能够把wait()方法放在不论什么同步控制方法里。不用考虑这个类是否继承Thread或者实现Runnable接口。
仅仅能在同步控制方法或同步控制块中调用wait()、notify()、notifyAll()的线程在调用这些方法前必须“拥有”(获取)对象的锁。(sleep不用操作锁。所以能够在非同步控制方法里调用)。
synchronized(x){
x.notify();
}