(二)线程同步基础

1.使用 synchronized实现同步方法

如果一个对象已经用synchronized声明,那么只允许一个执行线程访问它,如果其他线程试图访问这个对象的其他方法,它将被挂起,直到第一个线程执行完正在运行的方法。被synchronized声明的方法就是临界区。

对于非静态的方法被synchronized修饰后,同一时间内只能有一个线程访问这个对象的synchronized方法。即,每一个synchronized方法都控制着类成员变量的访问,要想访问被synchronized声明的方法,必须先获得该示例对象的锁,否则将会被挂起,这就保证了同一个实例的所有非静态synchronized的方法在同一个时刻只能有一个在执行。

而对于用synchronized声明的静态方法,同时只能被一个执行线程访问,但是其他线程可以访问这个对象的synchronized声明的非静态方法。必须谨慎这一点,当静态与非静态方法涉及到共享数据的时候就会导致数据不一致的问题。

使用synchronized关键子会减低程序的性能,因此只能在并发的情境中需要修改共享数据的地方使用它。

可以递归调用被synchronized声明的方法。当线程访问一个对象的同步方法时,他还可以调用这个对象的其他同步方法,也包含正在执行的方法,而不用再次获取方法的访问权。

我们可以使用synchronized关键字保护代码块(而不是整个方法的访问)。方法的其余部分保持在代码块之外,同时以保证synchronized代码块中的代码尽可能短,以获取更高的性能。

synchronized(this){
//java code;
}

当使用synchronized关键字保护代码块时,必须把对象引用做为传入参数,通常情况下使用this,但是也可以使用其他对象(非依赖对象)进行控制:

比如:

Object o = new Object();
synchronized(o){
//java code;
}

o即:与本类无关的对象。

2.在代码块中使用条件

public synchronized void set(){
   while(storage.size()==maxSize){
       try{
           wait();
       }catch(InterruptedException e){
          e.printStackTrace();
       }
   }
}

注意:被唤醒之后还要继续执行while中的条件判定。

3.wait()与notify()、notifyAll()

首先,调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(obj){......}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:

java.lang.IllegalMonitorStateException:current thread not owner

在调用wait的时候,线程自动释放其占有的对象锁,同时不会去申请对象锁。当线程被唤醒的时候,它才再次获得了去获得对象锁的权利。

所以,notify与notifyAll没有太多的区别,只是notify仅唤醒一个线程并允许它去获得锁,notifyAll是唤醒所有等待这个对象的线程并允许它们去获得对象锁,只要是在synchronied块中的代码,没有对象锁是寸步难行的。其实唤醒一个线程就是重新允许这个线程去获得对象锁并向下运行。

notifyAll,虽然是对每个wait的对象都调用一次notify,但是这个还是有顺序的,每个对象都保存这一个等待对象链,调用的顺序就是这个链的顺序。其实启动等待对象链中各个线程的也是一个线程,在具体应用的时候,需要注意一下。

wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能。因为都个对像都有锁,锁是每个对像的基础,当然操作锁的方法也是最基础了。

wait():

等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。

调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

notify():

唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

notifyAll():

唤醒所有等待的线程,注意唤醒的是notify之前wait的线程,对于notify之后的wait线程是没有效果的。

4.使用锁实现同步

这种对同步的控制比synchronized更强大也二更灵活。这种机制基于Lock接口及其实现类(例如:ReeNtrantLOCK),Lock实现了更复杂的临界区结构(即:控制的获取和释放不出现在一个块中)。tryLock()的实现提供了更多的作用。通过返回值得知是否有其他线程正在使用这个锁保护的代码块,返回true:线程获取了锁。false:表示没有获取锁。

ReentrantLock类也允许递归调用。

使用读写锁实现同步数据访问。ReadWriteLock接口和它的唯一实现类ReentrantReadWriteLock。这个类有两个锁,一个是读操作锁另一个是写操作锁。使用读操作锁可以允许多个线程同时访问,但是使用写操作锁时只能允许一个线程进行。

private ReadWriteLock lock = new ReadWriteLock ();
//获取读操作锁
lock.readLock().lock();
//释放读操作锁
lock.readLock().unlock();
//获取写操作锁
lock.writeLock().lock();
//释放写操作锁
lock.writeLock().unlock();

5.修改锁的公平性

ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数fair,为false即非公平模式,在非公平模式下当有很多线程在等待锁时,锁将随机选择一个来访问临界区。而为True时,则选择等待时间最长的获得临界区。

6.在锁中使用多条件

一个锁可能关联一个或者多个条件,这些条件通过Condition接口声明。目的是允许线程获取锁并且查看等待的某一个条件是否满足,Condition提供了挂起线程和唤起线程的机制。

用法如下:

ReentrantLock Lock = new ReentrantLock();
Condition line = lock.newCondition();
Condition space = lock.newCondition();
while(buffer.size()==maxSize){
    space.await();
}
line.signalAll();
while(buffer.size()==0){
    line.await();
}
space.signalAll();

注意:唤醒后还需要进行while循环。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-06 00:29:28

(二)线程同步基础的相关文章

C# 多线程(二) 线程同步基础(上)

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

C# 多线程(二) 线程同步基础

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

C#中的线程(二) 线程同步基础

1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具:                       简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程完成                       锁系统 构成 目的 跨进程? 速度 lock 确保只有一个线程访问某个资源或某段代码. 否 快 Mutex 确保只有一个线程访问某个资源或某段代码.可被用于防止一个程序的多个实例同时运行. 是 中等 Semaphore 确保不超过指定数目的线程访问某

第二章线程同步基础

Java 7 并发编程实战手册目录 代码下载(https://github.com/Wang-Jun-Chao/java-concurrency) 第二章线程同步基础 2.1简介 多个执行线程共享一个资源的情景,是最常见的并发编程情景之一.在并发应用中常常遇到这样的情景:多个线程读或者写相同的数据,或者访问相同的文件或数据库连接. 为了防止这些共享资源可能出现的错误或数据不一致,我们必须实现一些机制来防止这些错误的发生. 为了解决这些问题,引入了临界区(Critical Section)概念,临

.net中的线程同步基础(搬运自CLR via C#)

线程安全 此类型的所有公共静态(Visual Basic 中为 Shared)成员对多线程操作而言都是安全的.但不保证任何实例成员是线程安全的. 在MSDN上经常会看到这样一句话.表示如果程序中有n个线程调用这个方法,那么这n个线程都是安全的, 但是实例成员就不能保证了. 比如Math.Max方法,不管有多少个线程调用,都不会出现线程不安全的情况. 列举一个由于多线程引起的数据不安全. static void Main(string[] args) { Stopwatch watch = new

【java7并发编程实战】—–线程同步基础:synchronized

在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者.写相同的数据,访问相同的文件等等.对于这种情况如果我们不加以控制,是非常容易导致错误的.在java中,为了解决这个问题,引入临界区概念.所谓临界区是指一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问. 在java中为了实现临界区提供了同步机制.当一个线程试图访问一个临界区时,他将使用一种同步机制来查看是不是已经有其他线程进入临界区.如果没有则他就可以进入临界区,否则他就会被同步机制挂起,指定进入的线程离开这个临界区

线程同步基础之简介

多个执行线程共享一个资源的情景,是最常见的并发编程情景之一.在并发应用中常常遇到这样的情景:多个线程读或者写相同的数据,或者访问相同的文件或数据库连接.为了防止这些共享资源可能出现的错误或数据不一致,我们必须实现一些机制来防止这些错误的发生. 为了解决这些问题,人们引入了临界区(Critical Section)概念.临界区是一个用以访问共享资源的代码块,这个代码块在同一时间内只允许一个线程执行. 为了帮助编程人员实现这个临界区,Java提供了同步机制.当一个线程试图访问一个临界区时,它将使用一

线程同步基础之使用非依赖属性实现同步

当使用synchronized关键字来保护代码块时,必须把对象引用作为传入参数.通常情况下,使用this关键字来引用执行方法所属的对象,也可以使用其他的对象对其进行引用.一般来说,这些对象就是为这个目的而创建的.例如,在类中有两个非依赖属性,它们被多个线程共享,你必须同步每一个变量的访问,但是同一时刻只允许一个线程访问一个属性变量,其他某个线程访问另一个属性变量. 这里我们演示电影院售票场景.这个范例模拟了有两个屏幕和两个售票处的电影院.一个集票处卖出的一张票,只能用于其中一个屏幕,不能同时用于

线程同步基础之使用synchronized实现同步方法

Java的最基本的同步方式,即使用synchronized关键字来控制一个方法的并发访问. 每一个用synchronized关键字声明的方法都是临界区.在Java中,同一个对象的临界区,在同一时间只有一个允许被访问. 静态方法则有不同的行为.用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态的synchronized方法.必须非常谨慎这一点,因为两个线程可以同时访问一个对象的两个不同的synchronized方法,即其中一个是静态s