线程中的共享资源

java语言支持两种形式的线程:互斥与协作,实现这两种方式的机制就是监视器。java虚拟机通过对象锁来实现互斥,允许多个线程在同一个共享数据上独立而互不干扰的工作。协作则是通过Object类的wait()方法和notify方法来实现的,允许多个线程为同一目标而共同工作。互斥是帮助线程在访问共享数据时不被其它线程干扰,而协作帮助线程与其它线程共同工作。

记得以前高中的时候,夏天每次下了晚自习,回到宿舍的时候,大家都想去洗个澡。但是宿舍只有一个卫生间,所以小A总是第一个冲回宿舍,跑进卫生间就把门锁上了,然后在里面自由自在的冲凉。(其实并不清楚他是否在冲凉)其它人只能在外面等着了,只到小A打开门出来,第二个人才可以进去。这就是一个标准的互斥。

而协作就相当于我们在进行I/O操作的时候,需要一个”读线程“,从缓冲区中读取数据,而另一个”写线程“会向缓冲区中填充数据。”读线程“需要缓冲区处于一个”非空“的状态,这样它才可以从中读数据。如果”读线程“发现缓冲区是空的,它就必须等待。”写线程“就负责向缓冲区写数据,只有”写线程“完成一些数据的写入,”读线程“才能完成相应的读取操作。java虚拟机中把这种监视器称作”等待并唤醒“监视器。等待线程将自身挂起是因为监视器保护数据并不属于它想要继续执行正确操作的状态。同样,唤醒线程在它将监视器保护数据设置成等待线程想要的状态后执行唤醒命令。

为了实现监视器排他性的监视能力,Java虚拟机为每一个对象和类都关联一个锁(有时候被成为互斥体(mutex))。一个锁就像一种任何时候只允许一个线程”拥有“的特权。java语言提供了两种方式来标志监视区域:同步语句和同步方法。

同步语句:要建立一个同步语句,只需要在一个计算引用的表达式中加上synchronized关键字就可以了。代码示例如下:

package ticketsale;

public class SaleTickets1 implements Runnable {
	private static int tickets = 100;
	private void sale(){
		if(tickets > 0){
			synchronized(this){
				System.out.println(
						Thread.currentThread().getName()
						+"卖出了第"+tickets+"张票");
				tickets -- ;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println("此处可以进行其它操作...");
	}
	@Override
	public void run() {
		while(tickets > 0){
			sale();
		}
	}
	public static void main(String[] args) {
		SaleTickets1 ticket = new SaleTickets1();
		for(int i=1; i<=10; i++){
			Thread t = new Thread(ticket);
			t.setName("第"+i+"窗口:");
			t.start();
		}
	}
}

如果没有获得对当前对象this的锁,在同步块中的语句是不会执行的。如果使用的不是this引用,而是用一个表达式获得对另一个对象的引用,在线程语句体之前需要获得那个对象的锁。

同步方法:只需要在方法修饰符前加上synchronized关键字。代码示例如下:

package ticketsale;

public class SaleTicket implements Runnable {
	private static int tickets = 100;
	private synchronized void sale(){
		if(tickets > 0){
			System.out.println(Thread.currentThread().getName()
					+"卖出第:"+tickets+"张票");
			tickets --;
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	@Override
	public void run() {
		while(tickets > 0){
			sale();
		}
	}
	public static void main(String[] args) {
		SaleTicket sale = new SaleTicket();
			for(int i=0; i<10; i++){
				Thread t = new Thread(sale);
				t.setName("第"+i+"个窗口");
				t.start();
			}
	}

}

同步类方法(静态方法)和同步实例方法是相同的操作。不同之处在于同步语句的时候不使用this(类方法没有this),线程必须获得对应的Calss实例的锁。

上面的实例中就是我们常见的售票系统。其中所用的线程就是互斥。下面的实例则为协作式线程,也是一个常见的问题,就是生产者——消费者的问题。前面已经讲过协作式线程,下面直接上代码:

测试类:

package producersandconsumers;

public class ProducersAndConsumers {
	public static void main(String[] args) {
		Storage storage = new Storage();
		Thread consumer1 = new Thread(new Consumer(storage));
		Thread consumer2 = new Thread(new Consumer(storage));
		consumer1.setName("消费者1");
		consumer2.setName("消费者2");
		Thread producer1 = new Thread(new Producer(storage));
		Thread producer2 = new Thread(new Producer(storage));
		producer1.setName("生产者1");
		producer2.setName("生产者2");
		consumer1.start();
		consumer2.start();
		producer1.start();
		producer2.start();
	}
}

产品类:

package producersandconsumers;
/*
 * 产品类
 */
public class Product {
	private  int id;
	private String name;

	public Product(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String toString(){
		return "产品的id为:"+id+" 名称为:"+name;
	}
}

仓库类:

package producersandconsumers;
/**
 * 仓库类
 * @author Administrator
 */
public class Storage {
	//设置仓库容量
	private Product[] products = new Product[10];
	private int top = 0;
	//生产者向仓库里放入产品
	public synchronized void push(Product product){
		while(top == products.length){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//把产品放入仓库
		products[top++] = product;
		System.out.println(Thread.currentThread().getName()+"生产了产品:"+product);
		notifyAll();
	}
	//消费者把产品从仓库中取出来
	public synchronized	Product pop(){
		while(top == 0){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//从仓库中取出产品
		-- top;
		Product p = new Product(products[top].getId(),products[top].getName());
		products[top] = null;
		System.out.println(Thread.currentThread().getName()+"消费了产品:"+p);
		notifyAll();
		return p;
	}
}

消费者类

package producersandconsumers;

public class Consumer implements Runnable {
	private Storage storage;
	public Consumer(Storage storage){
		this.storage = storage;
	}
	@Override
	public void run() {
		for(int i=0; i<20; i++){
			storage.pop();
		}
	}

}

生产者类:

package producersandconsumers;

public class Producer implements Runnable {
	private Storage storage;
	public Producer(Storage storage){
		this.storage = storage;
	}
	@Override
	public void run() {
		for(int i=0; i<20; i++){
			Product product = new Product(100+i,"苹果");
			storage.push(product);
		}
	}

}

java SE5的java.util.concurrent类库中还定义了显示的Lock对象,可以显示的创建、锁定和释放。可以使程序的灵活性显得更加优雅。有机会大家自己可以去多了解。

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

时间: 2024-11-09 14:39:49

线程中的共享资源的相关文章

【java线程安全与共享资源】

原文链接 作者:Jakob Jenkov 译者:毕冉 校对:丁一 java学习中,允许被多个线程同时执行的代码称作线程安全的代码.线程安全的代码不包含竞态条件.当多个线程同时更新共享资源时会引发竞态条件.因此,了解Java线程执行时共享了什么资源很重要. 局部变量 局部变量存储在线程自己的栈中.也就是说,局部变量永远也不会被多个线程共享.所以,基础类型的局部变量是线程安全的.下面是基础类型的局部变量的一个例子: public void someMethod(){ long threadSafeI

AD中发布共享资源

实例:在AD中发布共享文件夹 实验环境:server01:DC          server02,server03:加入域 1:首先在server01上建共享文件夹public,共享权限为domain  users (server01)服务器管理器--工具--Active Directory用户个计算机--geng.com--右键--新建--共享文件夹 共享的名称,网络路径 这时会显示共享的文件夹 2:验证:在server02上资源管理器--左侧网络--上面网络--搜索active direc

【java项目实战】ThreadLocal封装Connection,实现同一线程共享资源

线程安全一直是程序员们关注的焦点.多线程也一直是比較让人头疼的话题,想必大家以前也遇到过各种各种的问题.我就不再累述了.当然,解决方案也有非常多,这篇博文给大家提供一种非常好的解决线程安全问题的思路. 首先.我们先简单的认识一下ThreadLocal,之后是实例+解析,最后一句话总结. 1.认识一下ThreaLocal 认识ThreadLocal必需要通过api文档,不只具有说服力,并且它会给你更加全面的解释.以下我我给大家从api文档上截取一张图,并标出来了七点需要重点理解的内容,实例过后的解

【JAVA并发】共享资源访问

什么是共享资源 先了解下JAVA程序在运行时内存的分布,由以下部分组成 堆:所有线程共享一个堆:存放的都是new 出来的对象: 方法区:所有线程共享一个方法区:里面存放的内容有点杂,可以认为是除堆和栈中的其它东西(如类信息,静态变量,常量,代码等): 程序计数器:也叫PC,存放下一条指令所在单元的地址的地方; JAVA栈:每个线程都有一个自己的JAVA栈;存放的一般是方法的局部变量,方法出口信息等: 本地方法栈:与JAVA栈类似,区别是使用的对象不一样,本地方法栈是给Native方法使用的,JA

线程中的等待与唤醒

上一篇文章讲到join()方法的时候,用到了一个函数即为yield()方法,它的作用是将cup让与具有相同优先级的线程去使用.本篇文章就为大家讲述线程中的一些关于等待和唤醒的函数. sleep(): 使当前线程暂停使用一段时间,让其它线程继续有机会执行,但是它并不释放对象锁,也就是说有synchronized关键字的时候,其它线程仍然不能访问其共享资源.sleep()方法需要用户去设置阻塞的时间.在用户设置sleep()方法的时候,该线程一定是不会被执行的,所以会让优先级低的线程有机会去执行.也

C++线程中的几种锁

线程之间的锁有:互斥锁.条件锁.自旋锁.读写锁.递归锁.一般而言,锁的功能越强大,性能就会越低. 1.互斥锁 互斥锁用于控制多个线程对他们之间共享资源互斥访问的一个信号量.也就是说是为了避免多个线程在某一时刻同时操作一个共享资源.例如线程池中的有多个空闲线程和一个任务队列.任何是一个线程都要使用互斥锁互斥访问任务队列,以避免多个线程同时访问任务队列以发生错乱. 在某一时刻,只有一个线程可以获取互斥锁,在释放互斥锁之前其他线程都不能获取该互斥锁.如果其他线程想要获取这个互斥锁,那么这个线程只能以阻

关于java多线程任务执行时共享资源加锁的方式思考

1.加锁方式: 1-1.使用synchronized关键字进行方法或代码块的加锁方式 1-2.使用ReentrantLock类提供的lock()方法的方式 2.代码实现(传统的银行取款存款问题): 2-1.Account.java类:账户类 package com.java.thread; import java.util.concurrent.locks.ReentrantLock; /** * 账户类 * @author steven * */ public class Account {

Windows Server 2008共享资源所遇到问题设置步骤详解

很多人认为访问共享资源很简单,只要先找到共享资源,之后双击共享目标,再登录进去就可以进行访问操作了.事实上,这其中的每一步操作都可能会受到Windows系统的限制;这不,当我们尝试访问Windows Server 2008系统中的共享资源时,该系统就对其中的每一个环节设置了障碍,我们必须对症下药进行合理设置,才能扫清障碍,让共享访问一路绿灯. 1.扫清看不见障碍 按理来说,从局域网中的普通计算机中,打开网上邻居窗口就能看到目标共享资源了;可是,Windows Server 2008系统中的共享资

共享资源加锁的操作方法-10-多线程

1 在多线程的编程环境中,锁的使用必不可少! 2 于是,今天来总结一下为共享资源加锁的操作方法. 3 4 一.使用synchronized方式 5 6 //线程1 7 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 8 @synchronized(_myLockObj){ 9 [obj1 method1]; 10 sleep(30); 11 } 12 @synchronized(ob