学习笔记(九)并发(三)

《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();
}
时间: 2024-11-24 02:13:35

学习笔记(九)并发(三)的相关文章

多线程编程学习笔记——使用并发集合(三)

接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 四.   使用ConcurrentBag创建一个可扩展的爬虫 本示例在多个独立的即可生产任务又可消费任务的工作者间如何扩展工作量. 1.程序代码如下. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Sy

APUE 学习笔记(九) 高级I/O

1. 非阻塞I/O 低速系统调用时可能会使进程永远阻塞的一类系统调用,包括以下调用: (1)某些文件类型你(网络socket套接字.终端设备.管道)暂无可使用数据,则读操作可能会使调用者永远阻塞 (2)如果数据不能立即被(1)中文件类型接受,则写操作会使调用者永远阻塞 (3)某些进程间通信函数 非阻塞I/O使我们可以调用open.read.write这样的I/O操作,并使这些操作不会永远阻塞,如果这种操作不能完成,则调用立即出错返回 对于一个给定的文件有两种方法对其指定非阻塞I/O: (1)调用

python学习笔记九——文件与目录

1.python进行文件读写的函数是open或file类 mode:r  只读 r+   读写 w  写入,先删除原文件,再重新写入,如果文件没有则创建 w+  读写,先删除原文件,再重新写入,如果文件没有则创建(可写入和输出) a  写入,在文件末尾追加新的内容,文件不存在则创建 a+  读写,在文件末尾追加新的内容,文件不存在则创建 b  打开二进制文件,可与r,w,a,+结合使用 U  支持所有的换行符号,"\r","\n","\r\n"

jQuery学习笔记之过滤器三(向上查找兄弟元素、向下查找兄弟元素)

向上查找兄弟元素的方法:prev方法.prevAll方法.prevUntil方法 向下查找兄弟元素:next方法.nextAll方法.nextUntil方法 向上查找兄弟元素 1.prev方法 2.prevAll方法 3.prevUntil方法 向下查找兄弟元素 1.next方法 2.nextAll方法 3.nextUntil方法 jQuery学习笔记之过滤器三(向上查找兄弟元素.向下查找兄弟元素)

义隆单片机学习笔记之(三) 应用例程

常用寄存器: 0x01 (R1) 计时器 0x02 (R2)程序计数器 PC 0x03 (R3)状态寄存器 0x04 (R4)间址寄存器 0x05 (R5)IO PORT 5 0x06 (R6)IO PORT 6 ----- (IOC5)P5的输入输出配置 ----- (IOC6)P6的输入输出配置 0x0f (ISR,读)中断信号指示寄存器(第三位有效,分别对应于3个中断源) 0x0f (IOCF,写)中断屏蔽标志 0x0E (IOCE)(IO60作为中断输入的配置与看门狗的开关在一个寄存器中

angular学习笔记(九)-css类和样式3

再来看一个选择li列表的例子: 点击li中的任意项,被点击的li高亮显示: <!DOCTYPE html> <html ng-app> <head> <title>6.3css类和样式</title> <meta charset="utf-8"> <script src="../angular.js"></script> <script src="scri

angular学习笔记(九)-css类和样式2

在上一个例子中,元素的类名使用拼接的方法,这样,类名中就不得不带有true或false,并且不易维护,所以,angular使用ng-class属性来控制元素的类名: 我们来看一个小例子,点击error按钮,顶部提示错误框,点击warning按钮,顶部提示警告框. 错误框的类名是.err,警告框的类名是.warn: <!DOCTYPE html> <html ng-app> <head> <title>6.2css类和样式</title> <

Linux System Programming 学习笔记(九) 内存管理

1. 进程地址空间 Linux中,进程并不是直接操作物理内存地址,而是每个进程关联一个虚拟地址空间 内存页是memory management unit (MMU) 可以管理的最小地址单元 机器的体系结构决定了内存页大小,32位系统通常是 4KB, 64位系统通常是 8KB 内存页分为 valid or invalid: A valid page is associated with an actual page of data,例如RAM或者磁盘上的文件 An invalid page is

Git 学习笔记&lt;分支管理&gt; (三)

分支是什么? 分支就像树分出的树枝,不同的是,它们之间可以互相合并. 将版本的推进想象成一个链表的伸长:  version 1.0 ==> version 2.0 ==>version3.0  . master是主要的分支基本上用于发布产品.你可以从master分出一个dev,在上面创建新功能,或者修bug然后调试.最后再合并到master里面.就像下面这样. master分支:  version 1.0=========>version 2.0===... \            

APUE学习笔记:第三章 文件I/O

3.1 引言 术语不带缓冲指的是每个read和write都调用内核中的一个系统调用.这些不带缓冲的I/O函数不是ISO C的组成部分,但是,它们是POSIX.1和Single UNIX Specification的组成部分 3.2 文件描述符 UNIX系统shell使用文件描述符0与进程的标准输入相关联.文件描述符1与标准输出相关联.文件描述符2与标准出错输出相关联. 在依从POSIX的应用程序中,幻数0.1.2应当替换成符号常量STDIN_FILENO,STDOUT_FILENO和STDERR