java中的共享数据的并发处理(J2SE入门15)

共享数据的并发处理 

多线程同时并发访问的资源叫做临界资源。

多个线程同时访问对象并要求操作相同资源时分割了原子操作就会出现问题。(原子操作,不可再分的操作)会出现数据的不一致或数据不完整,为避免这种现象采用对访问的线程做限制的方法。

互斥锁机制,利用每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个琐是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程

1.Synchronized修饰代码块(同步代码块),
   public void push(char c)
         synchronized(this){//只有持有当前对象的锁标记才能访问这个代码块
         ... 
         } 
  }

对括号内的对象加锁,只有拿到锁标记的对象才能执行该代码块

2.Synchronized修饰方法
   public synchronized void push(char c) 
      ... 
  } 
对当前对象的加锁,只有拿到锁标记的对象才能执行该方法。

注意:构造方法不能Synchronized修饰静态方法可以用Synchronized修饰(是对类对象加锁),抽象方法不能用Synchronized修饰,不影响子类覆盖,子类在覆盖这个方法是可以加Synchronized,也可以不加Synchronized,所以根据Java不允许写废代码的特点是不能写在一起。

注意:对当前对象加锁,一个代码块或者方法是同步的(Synchronized),当前对象的锁标记没有分配出去时,有一个线程来访问这个代码块时,就会的到这个对象的锁标记,直到这个线程结束才会释放着个锁标记,其他想访问这个代码块或者是方法线程就会进入这个对象锁池,如果没有得到当前对象的锁标记,就不能访问这个代码块或者是方法。当一个线程想要获得某个对象锁标记而进入锁池,这个线程又持有其他对象的锁标记,那么这个线程也不会释放持有的锁标记。

注:方法的Synchronized特性本身不会被继承,只能覆盖
线程因为未拿到锁标记而发生阻塞进入锁池(lock pool)。每个对象都有自己的一个锁池的空间,用于放置等待运行的线程。由系统决定哪个线程拿到锁标记并运行。

使用互斥锁的注意事项

举例:男孩和女孩例子,每个女孩是一个对象,每个男孩是个线程。每个女孩都有自己的锁池。每个男孩可能在锁池里等待。
class Gril {
    public void hand() {
    }
    public synchronized void kiss() {
    }
}
class Boy extends Thread {
    public void run() {
    }
}


注意:只读不用加同步,只写也不用加同步,只有读写操作兼而有之时才加同步

注意:在java.io包中Vector 和 HashTable 之所以是线程安全的,是因为每个方法都有synchronized修饰,Static 方法可以加 synchronized , 锁的是类对象。但是Vector 是 jdk 1.0 的  ArrayList 是 jdk1.2 所以实际应用还是使用ArrayList。

注意:内同步,外同步。内同步,即,类内的方法加同步(synchronized)修饰,外同步即,在需要控制只能由一个线程进行访问时,把需要控制的方法写在同步代码块里。

死锁问题

多线程不释放自己拥有的锁标记,而想申请其他线程拥有的锁标记,就会造成死锁。

没有获得加锁对象的锁标记的线程,不能访问只有获得该对象所标记才能访问的同步方法,但可以访问这个对象的非同步的方法

死锁的两种处理方法
统一排列锁顺序(解决不同方法中对多个共享资源的访问)
对象1的方法
synchronized(a)
synchronized(b)
对象2的方法
synchronized(a)
synchronized(b)

java线程间通信(也就是线程间的相互协调)

等待通知机制
线程间通信使用的空间称之为对象的等待对列(wait pool),该队列也是属于对象的空间的。


进入等待池

使用Object类中wait()的方法,在运行状态中,线程调用wait(),此时表示线程将释放自己所有的锁标记和CPU的占用,同时进入这个对象的等待池。等待池的状态也是阻塞状态,只不过线程释放自己的锁标记。只有在对该对象加锁的同步代码块里,才能掉用该对象的wait(),表示线程将会释放所有锁标记,进入等待队列,线程将进入等待队列状态。

一个线程进入了对一个对象加锁的同步代码块,并对该对象调用了wait()方法,释放自己拥有的所有锁标记,进入该对象等待队列,另一个线程获得了该对象的锁标记,进入代码块对该对象调用了notify()方法(如果对该对象调用了notifyAll()方法,就会使放等待队列里所有的线程),对该对象调用方法的线程也不会释放所拥有的锁标记(对自身没有影响),也就是从等待队列里释放出一线程,释放出的这个线程要继续运行也就还要进入那个同步代码块,因为得不到要访问代码块对象的锁标记,而进入该对象的锁池,等待所标记释放。

注意:用notifyAll()取代notify(),因为在调用notify()方法时,是由系统决定释放出哪个线程
退出等待池进入锁池

notify():将从对象的等待池中移走一个任意的线程,并放到锁池中,那里的对象一直在等待,直到可以获得对象的锁标记。
notifyAll(): 将从等待池中移走所有等待那个对象的线程并放到锁池中,只有锁池中的线程能获取对象的锁标记,锁标记允许线程从上次因调用wait()而中断的地方开始继续运行

注意:只能对加锁的资源进行wait()和notify()

1) wait():交出锁和CPU的占用; 
2) notify():将从对象的等待池中移走一个任意的线程,并放到锁池中,那里的对象一直在等待,直到可以获得对象的锁标记。 
3) notifyAll(): 将从等待池中移走所有等待那个对象的线程并放到锁池中,只有锁池中的线程能获取对象的锁标记,锁标记允许线程从上次因调用wait()而中断的地方开始继续运行

posted @ 2011-05-03 15:32  BuildNewApp  阅读(581)  评论(0编辑  收藏  举报