黑马程序员 Java学习总结之同步、线程间通信

来源:互联网 发布:淘宝男模特红人 编辑:程序博客网 时间:2024/05/01 08:10

------- android培训、java培训、期待与您交流! ----------

多线程里数据的安全问题地处理是很可以显示一个Java程序员的功力的。但其实也没有什么,Java是很好学的东西,因为早有大师把问题的解决办法封装成一套机制还有几个API了。所以我们解决问题的难度就小很多了。

闲话少说。多线程安全问题的原因在于几个线程操作共享数据,一个线程对这个数据操作的语句还没有执行完,另一个线程获取CPU执行权中断上个线程对这个数据的完整操作。如果在操作共享数据数据时,只允许让一个线程都执行完后,才可以让其它线程操作共享数据,问题就可以解决了。而Java就是这么解决多线程操作共享数据安全问题的。

这个解决办法的体现方式就是同步(Synchronization),包括同步方法和同步代码块以及静态同步方法。

同步方法示例

<pre name="code" class="java">public synchronized void foo1() {    System.out.println("synchronized methoed");}

同步代码块示例:

public void foo2() {    synchronized (myObj) {        System.out.println("synchronized methoed");    }}
静态同步方法示例:

public static synchronized void foo1() {    System.out.println("synchronized methoed");}

在使用同步时请记住下面的基本规则:

第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

要想彻底理解同步就要清楚一个概念——同步锁。在java中,每一个对象有且仅有一个同步锁。对象可以作为同步锁,同样类也可以作为同步锁。上面的1、2示例就是用一个对象作为同步锁。其中示例1作为同步锁的对象就是this,也就是调用这个函数的那个对象。示例2是一个其它什么对象。上面说过每一个Java对象有且仅有一个同步锁,所以不仅限this,理论上每个对象都可以作为同步锁,只要希望同步的线程使用的是同一个同步锁也就是同一个对象就行。示例3是示例1的特殊情况,它的锁就是这个函数所在的类,当然类始终只有一份了。

下面给出一个多线程操作共享数据的经典例子——生产者和消费者。这个经典的问题还涉及到线程的等待唤醒机制,所以还得再介绍几个概念:

notify()        -- 唤醒在此对象监视器上等待的单个线程。
notifyAll()    -- 唤醒在此对象监视器上等待的所有线程。
wait()          -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)          -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos)      -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

生产者消费者问题源代码:

// Demo1.java// 仓库class Depot {    private int capacity;    // 仓库的容量    private int size;        // 仓库的实际数量    public Depot(int capacity) {        this.capacity = capacity;        this.size = 0;    }    public synchronized void produce(int val) {        try {             // left 表示“想要生产的数量”(有可能生产量太多,需多此生产)            int left = val;            while (left > 0) {                // 库存已满时,等待“消费者”消费产品。                while (size >= capacity)                    wait();                // 获取“实际生产的数量”(即库存中新增的数量)                // 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库)                // 否则“实际增量”=“想要生产的数量”                int inc = (size+left)>capacity ? (capacity-size) : left;                size += inc;                left -= inc;                System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n",                         Thread.currentThread().getName(), val, left, inc, size);                // 通知“消费者”可以消费了。                notifyAll();            }        } catch (InterruptedException e) {        }    }     public synchronized void consume(int val) {        try {            // left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费)            int left = val;            while (left > 0) {                // 库存为0时,等待“生产者”生产产品。                while (size <= 0)                    wait();                // 获取“实际消费的数量”(即库存中实际减少的数量)                // 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”;                // 否则,“实际消费量”=“客户要消费的数量”。                int dec = (size<left) ? size : left;                size -= dec;                left -= dec;                System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n",                         Thread.currentThread().getName(), val, left, dec, size);                notifyAll();            }        } catch (InterruptedException e) {        }    }    public String toString() {        return "capacity:"+capacity+", actual size:"+size;    }} // 生产者class Producer {    private Depot depot;        public Producer(Depot depot) {        this.depot = depot;    }    // 消费产品:新建一个线程向仓库中生产产品。    public void produce(final int val) {        new Thread() {            public void run() {                depot.produce(val);            }        }.start();    }}// 消费者class Customer {    private Depot depot;        public Customer(Depot depot) {        this.depot = depot;    }    // 消费产品:新建一个线程从仓库中消费产品。    public void consume(final int val) {        new Thread() {            public void run() {                depot.consume(val);            }        }.start();    }}public class Demo1 {      public static void main(String[] args) {          Depot mDepot = new Depot(100);        Producer mPro = new Producer(mDepot);        Customer mCus = new Customer(mDepot);        mPro.produce(60);        mPro.produce(120);        mCus.consume(90);        mCus.consume(150);        mPro.produce(110);    }}

最后,一个问得比较多的问题是为什么notify(), wait()等函数定义在Object中,而不是Thread中:


Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

0 0
原创粉丝点击