多线程之----线程互斥再续

Posted Bruce小院

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程之----线程互斥再续相关的知识,希望对你有一定的参考价值。

本篇接上一篇 多线程之----线程互斥

不好意思 在上一篇中本来是要说线程互斥的,结果对比了下quartor和Timer,讲的quartor有点多了。这节我们重点说一下线程互斥。

按照如下的方式来学习线程互斥:

实现线程互斥的两种方法,sychronized wait/notify  lock(read/write).三种方式。

先开始第一种使用sychronized关键字。大家对这个应该不会陌生,因为我们写过很多线程安全或者说是线程互斥的例子,使用sychronized关键字应该是最主要的方式,在使用这个关键字修饰方法或者代码块的时候能够保证在同一时刻最多只有一个线程执行该段代码先看一个代码的示例吧:

这是我使用synchronized关键字写的一个饿汉式的单例模式,这里我们不是来说设计模式的,主要是拿这个当一个例子,来说明synchronized这个关键字的使用。

在上面的代码块中我们可以看到   getInstance这个方法是被synchronized关键字修改的方法,在这个方法中有一个synchronized的代码块,这里我写的这个是为了演示,其实不应该在getInstance这个方法上再加synchronized这个关键字,因为在方法内已经使用了,两个地方都加的话可能会导致死锁。然后我们再说一下synchronized这个关键字的参数,或者叫锁对象,这个锁对象我们可以类比现实生活中的锁。这个锁有两种一种是对象锁 一种是类所,其实类锁也是对象锁。当一个方法或者代码块被锁锁定后,其他的对象想使用这把锁的时候发现这把锁处理锁定的状态他就不能去访问被锁定的内容。知道对象锁被释放的时候才可以进去方法,这样就实现了线程的互斥。当然这个锁可以是其他的任何类型只要保证是同一把锁就可以。比如使用一个string 或者int 只要是对象都可以被用来当锁定的锁。

下面我们说一下被synchronized修改的方法和代码块的区别:

1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:  
public synchronized SingleTon getInstance();  
synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能

执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行

状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有

一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)

。  
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成

员变量的访问。  
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为

synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可

以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供

了更好的解决办法,那就是 synchronized 块。  
2. synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:  
synchronized(syncObject) {  
//允许访问控制的代码  
}  
synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机

制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。  
对synchronized(this)的一些理解 
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线

程必须等待当前线程执行完这个代码块以后才能执行该代码块。  
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized

(this)同步代码块。  
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)

同步代码块的访问将被阻塞。  
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个

object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。 

最后对synchronized的理解 要理解清楚使用的锁是哪个锁,锁要保持一致。

上面是对synchronized这个关键字来实现线程互斥的学习总结。

下面开始通过使用 wait和notify来实现互斥

先说一下两者的关系吧:

在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。明白这个原理,就能理解为什么synchronized(this)与synchronized(static XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。如果只是简单的想要实现在JAVA中的线程互斥,明白这些基本就已经够了。但如果需要在线程间相互唤醒的话就需要借助Object.wait(), Object.nofity()了。

我们知道线程的状态有创建,就绪 堵塞,停止,几种状态。在线程的活动中可以通过wait和notify来动态的改变线程的运行状态。wait和nitify要结合synchronized使用。我们结合一个很经典的面试题来说一下如何玩转synchronized和wait和nitify。

面试题目如下:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC   手写代码。

我想大家看到这个题目的时候如果你对wait和notify以及synchronized之间的使用很了解,思路很清晰的话 这很简单,好了 先上我写的代码看看再说。

public class NotifyAndWaitDemo implements Runnable {

    /**
     * 线程名字
     */
    private String name;
    /**
     * 上一个线程
     */
    private Object prev;
    /**
     * 自己
     */
    private Object self;

    /**
     * 构造方法
     * 
     * @param name
     * @param pre
     * @param self
     */
    public NotifyAndWaitDemo(String name, Object pre, Object self) {
        this.name = name;
        this.prev = pre;
        this.self = self;
    }

    @Override
    public void run() {
        int count = 10;
        while (count > 0) {
            synchronized (prev) {
                synchronized (self) {
                    System.out.print(name);
                    count--;
                    self.notify();
                }
                System.out.println("eeeee");
                try {
                    prev.wait();
                    System.out.println("ooooooooooo");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        NotifyAndWaitDemo pa = new NotifyAndWaitDemo("A", c, a);
        NotifyAndWaitDemo pb = new NotifyAndWaitDemo("B", a, b);
        NotifyAndWaitDemo pc = new NotifyAndWaitDemo("C", b, c);

        new Thread(pa).start();
        new Thread(pb).start();
        new Thread(pc).start();

    }
}

上面的代码自己先理解一下。今天关于wait和notify结合synchronized的学习先到这里,晚上有国足VS韩国的12强赛,准备要看球去了。对这个代码的解释以及wait,notify如何与synchronized的结合使用下次我们详细讲一下。

对剩下的lock(read/write)的讲解我们也在下一讲中来学习。

 

以上是关于多线程之----线程互斥再续的主要内容,如果未能解决你的问题,请参考以下文章

Linux多线程同步之互斥量和条件变量

多线程之synchronized讲解

python并发编程之多线程守护系列互斥锁生产者消费者模型

多线程编程之数据访问互斥

C++11多线程 原子操作概念及范例

Linux 多线程:线程安全之同步与互斥的理解