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

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

不好意思 在上一篇中本来是要说线程互斥的,结果对比了下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)的讲解我们也在下一讲中来学习。

时间: 2024-10-29 04:57:18

多线程之----线程互斥再续的相关文章

Python之多线程:线程互斥与线程同步

一.锁在多线程中的使用:线程互斥 lock = threading.Lock()#创建一个锁对象 1.with lock: pass 和进程使用的方式相同 2.控制线程结束的时间 通过一个全局变量 # encoding=utf-8 import threading,time,Queue,random exitFlag = False def write(lock,queue): while exitFlag != True: with lock: data = random.randint(1,

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)

线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public class Thread implements Runnable { /* What will be run. */ private Runnable target; ...... /** * Causes this thread to begin execution; the Java Virtu

27 Apr 18 GIL 多进程多线程使用场景 线程互斥锁与GIL对比 基于多线程实现并发的套接字通信 进程池与线程池 同步、异步、阻塞、非阻塞

27 Apr 18 一.全局解释器锁 (GIL) 运行test.py的流程: a.将python解释器的代码从硬盘读入内存 b.将test.py的代码从硬盘读入内存  (一个进程内装有两份代码) c.将test.py中的代码像字符串一样读入python解释器中解析执行 1 .GIL:全局解释器锁 (CPython解释器的特性) In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple na

线程间通信和线程互斥

线程间通信 1> 线程间通信分为两种 主线程进入子线程(前面的方法都可以) 子线程回到主线程 2> 返回主线程 3> 代码 这个案例的思路是:当我触摸屏幕时,会在子线程加载图片,然后在主线程刷新UI界面 视图布局我就不写了,大家自己来吧,线程间通信代码如下: #pragma mark - 添加响应方法触发创建子线程并加载数据 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

C#多线程学习(六) 互斥对象

C#多线程学习(六) 互斥对象 如何控制好多个线程相互之间的联系,不产生冲突和重复,这需要用到互斥对象,即:System.Threading 命名空间中的 Mutex 类. 我们可以把Mutex看作一个出租车,乘客看作线程.乘客首先等车,然后上车,最后下车.当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车.而线程与Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待Mutex对象被释放,如果它等待的Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mute

JAVA 并发编程-传统线程互斥技术(Synchronized)(三)

java线程互斥是为了保证,同一时刻最多只有一个线程执行该段代码.那么它的出现又是为了解决什么问题呢?账户存取款,在同一时间段只能让一个人进行操作. 下面来看一个简单实例(多线程带来的问题): public class TraditionalThreadSynchronized { /** * @param args */ public static void main(String[] args) { new TraditionalThreadSynchronized().init(); }

多线程同步之互斥对象

多线程同步之互斥对象 作者:vpoet mail:[email protected] 在http://blog.csdn.net/u013018721/article/details/46637215一文中介绍了使用临界区 对卖票问题进行线程间同步,本文将在上文的基础上,使用互斥对象对线程进行同步. 首先看看windows API 该函数创建一个命名或者不命名的互斥对象 lpMutexAttributes [in] Pointer to a SECURITY_ATTRIBUTES structu

mfc小工具开发之定时闹钟之---多线程急线程同步

一.MFC对多线程编程的支持 MFC中有两类线程,分别称之为工作者线程和用户界面线程.二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环. 工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等.用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等.但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务. 在MFC中,一般用全局函数Afx