`

多线程——同步(synchronized)下

    博客分类:
  • java
阅读更多

接着上一讲《多线程——同步(synchronized)上

 

上一讲中说到的第二个例子,通过synchronized块,指定获取对象锁来达到同步的目的。那有没有其它的方法,可以通过synchronized方法来实现呢?

 

根据同步的原理:如果能获取一个共享对象锁或类锁,及可实现同步。那么我们是不是可以通过共享一个类锁来实现呢?

 

是的,我们可以使用静态同步方法,根据静态方法的特性,它只允许类对象本身才可以调用,不能通过实例化一个类对象来调用。那么如果获得了这个静态方法的锁,也就是获得这个类锁,而这个类锁都是TestThread类锁,及达到了获取共享类锁的目的。

 

实现代码如下:

package thread_test;

/**
 * 测试扩展Thread类实现的多线程程序 
 * 
 * @author ciding 
 * @createTime Dec 7, 2011 9:37:25 AM
 *
 */
public class TestThread extends Thread{ 
	private int threadnum;
	
	public TestThread(int threadnum) { 
        this.threadnum = threadnum; 
    }
	
    public static synchronized void staticTest(int threadnum) { 
		for(int i = 0;i<1000;i++){ 
            System.out.println("NO." + threadnum + ":" + i );
        } 
    } 

    public static void main(String[] args) throws Exception { 
    	for(int i=0; i<10; i++){
        	new TestThread(i).start();
        	Thread.sleep(1);
    	}
    } 
    
    @Override
    public void run(){
    	staticTest(threadnum);
    }
}

 运行结果略,与例二中一样。

 

 

以上的内容主要是说明两个问题:同步块与同步方法。

1,同步块:获取的对象锁是synchronized(flag)中的flag对象锁。

2,同步方法:获取的是方法所属的类对象,及类对象锁。

     静态同步方法,由于多个线程都会共享,所以一定会同步。

     而非静态同步方法,只有在单例模式下才会同步。

 

 

接下来说一说能在synchronized内部运行的wait方法、notify方法与notifyAll方法。

先看一段JDK API 1.6 文档的原话:

JDK API 1.6 文档 java.lang.Object类中的wait()方法:
wait
public final void wait()
throws InterruptedException在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。

对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用

synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。

抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者
InterruptedException - 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态 被清除。
另请参见:
notify(), notifyAll()

 

JDK API 1.6 文档 java.lang.Object类中的notify()方法:
notify
public final void notify()

唤醒在此对象监视器上等待的单个线程如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。

此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:

通过执行此对象的同步实例方法。
通过执行在此对象上进行同步的 synchronized 语句的正文。
对于 Class 类型的对象,可以通过执行该类的同步静态方法。
一次只能有一个线程拥有对象的监视器。


抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
另请参见:
notifyAll(), wait()

 

JDK API 1.6 文档 java.lang.Object类中的notifyAll()方法:
notifyAll
public final void notifyAll() 

唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。

此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。


抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
另请参见:
notify(), wait()

 

根据synchronized能够获取对象锁,及获取对象监视器,所以这几个方法在同步中利用也是天衣无缝。

 

本来打算在线程最后的章节来实现生产者消费者模式的,这里就先实现个简单的试试水;好让童鞋们更顺水的了解这三个方法的使用。

 

例子1

package thread;

/**
 * main主函数
 * 
 * @author ciding 
 * @createTime Dec 12, 2011 3:05:44 PM
 *
 */
public class ConsumerAndProducerThread {
	public static void main(String[] args) {
		Storage storage = new Storage(40,100);
		
		/**
		 * 一共消费了60+61+62+63+64=310
		 */
		for(int i=0; i<5; i++){
			Consumer c = new Consumer(60+i, storage);
			c.start();
		}
		
		/**
		 * 一共生产了10+20+30+40+15+25+35+45+49+51=320 再加上初始化的40 一共360
		 */
		Producer p1 = new Producer(10,storage);		//生产10
		Producer p2 = new Producer(20,storage);		//生产20
		Producer p3 = new Producer(30,storage);		//生产30
		Producer p4 = new Producer(40,storage);		//生产40
		Producer p5 = new Producer(15,storage);		//生产15
		Producer p6 = new Producer(25,storage);		//生产25
		Producer p7 = new Producer(35,storage);		//生产35
		Producer p8 = new Producer(45,storage);		//生产45
		Producer p9 = new Producer(49,storage);		//生产49
		Producer p10 = new Producer(51,storage);	//生产51
		
		p1.start();
		p2.start();
		p3.start();
		p4.start();
		p5.start();
		p6.start();
		p7.start();
		p8.start();
		p9.start();
		p10.start();
	}
}

/**
 * 生产者
 * 
 * @author ciding 
 * @createTime Dec 12, 2011 3:05:35 PM
 *
 */
class Consumer extends Thread {
	private int neednum; 		// 生产产品的数量
	private Storage storage; 	// 仓库

	Consumer(int neednum, Storage storage) {
		this.neednum = neednum;
		this.storage = storage;
	}

	public void run() {
		storage.consume(neednum);// 消费指定数量的产品
	}
}

/**
 * 消费者
 * 
 * @author ciding 
 * @createTime Dec 12, 2011 3:06:03 PM
 *
 */
class Producer extends Thread {
	private int neednum; 		// 生产产品的数量
	private Storage storage; 	// 仓库

	Producer(int neednum, Storage storage) {
		this.neednum = neednum;
		this.storage = storage;
	}

	public void run() {
		storage.produce(neednum);	// 生产指定数量的产品
	}
}

/**
 * 仓库
 * 
 * @author ciding 
 * @createTime Dec 12, 2011 3:06:31 PM
 *
 */
class Storage {
	public int max_size = 100;	// 库存量初始值
	public int curnum = 0;		// 当前库存量初始值

	Storage() {
	}

	Storage(int curnum, int max_size) {
		this.curnum = curnum;
		this.max_size = max_size;
	}

	/**
	 * 生产指定数量的产品
	 * 
	 * @param neednum
	 */
	public synchronized void produce(int neednum) {
		/* 测试是否需要生产 */
		while (neednum + curnum > max_size) {
			try {
				Thread.sleep(100);	//模拟运行时间
				
				System.out.println("要生产的产品数量" + neednum + "超过剩余库存量"
					+ (max_size - curnum) + ",暂时不能执行生产任务!");
				wait();		// 当前的生产线程等待
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		curnum += neednum; // 满足生产条件,则进行生产,这里简单的更改当前库存量
		System.out.println("已经生产了" + neednum + "个产品,现仓储量为" + curnum);

		notify(); // 唤醒在此对象监视器上等待的某个线程			
	}

	/**
	 * 消费指定数量的产品
	 * 
	 * @param neednum
	 */
	public synchronized void consume(int neednum) {
		// 测试是否可消费
		while (curnum < neednum) {
			try {
				Thread.sleep(100);//模拟运行时间

				System.out.println("仓储量为:" + curnum + ",不够消费:" + neednum);
				wait();// 当前的生产线程等待
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		curnum -= neednum; // 满足消费条件,则进行消费,这里简单的更改当前库存量
		System.out.println("已经消费了" + neednum + "个产品,现仓储量为" + curnum);

		notify(); // 唤醒在此对象监视器上等待的某个线程
	}
}

 

运行结果:
仓储量为:40,不够消费:60
已经生产了51个产品,现仓储量为91
要生产的产品数量49超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量45超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量35超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量25超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量15超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量40超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量30超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量20超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量10超过剩余库存量9,暂时不能执行生产任务!
已经消费了64个产品,现仓储量为27
仓储量为:27,不够消费:63
仓储量为:27,不够消费:62
仓储量为:27,不够消费:61
已经生产了49个产品,现仓储量为76
已经消费了60个产品,现仓储量为16
已经生产了35个产品,现仓储量为51
已经生产了25个产品,现仓储量为76
已经生产了15个产品,现仓储量为91
要生产的产品数量40超过剩余库存量9,暂时不能执行生产任务!
要生产的产品数量45超过剩余库存量9,暂时不能执行生产任务!

线程没有关闭,一直还在wait。

 

细心的童鞋,应该已经看出问题的所在,在调用wait后,程序是通过调用notify()方法来唤醒在此对象监视器上等待的某个线程。

 

另外,根据输出的结果,我们来分析一个wait()与notify()的调用。

a为能运行的生产者 a=10

b为能运行的消费者 b=5

 

0:调用消费者,不成功,等待。(a=10,b=4)

1:调用了生产者,成功,唤醒了一个生产者/消费者。(a=1,b=5)

2:调用了10个生产,其中9个等待。(a=0,b=5)

3:调用一个消费,成功,然后调用notify()唤醒一个生产。(a=1,b=4)

4:连着调用三个消费,不成功,等待。(a=1,b=1)

5:调用唤醒的那个生产者,生产,成功,唤醒了一个生产者/消费者。(a=1,b=1)或略

6:调用消费者,成功,唤醒了一个生产者/消费者。(a=2,b=0)或略

7:调用唤醒的那个生产者,生产,成功,唤醒了一个生产者/消费者。(a=2,b=0)或略

8:调用唤醒的那个生产者,生产,成功,唤醒了一个生产者/消费者。(a=2,b=0)或略

9:调用唤醒的那个生产者,生产,成功,唤醒了一个生产者/消费者。(a=2,b=0)或略

10:调用唤醒的那个生产者,不成功,等待。(a=1,b=0)或略

11:调用唤醒的那个生产者,不成功,等待。(a=0,b=0)或略

 

到此,可运行的线程为0,都在等待状态。

因为两个程序等待的对象都是curnum(产品量),所以唤醒的线程可能是生产者也可能是消费者。

 

如果将程序中的notify()方法换成notifyAll()方法,及可实现简单的生产者消费者。

那是不是两个notify()方法都要换呢?

这一点,就留给童鞋们自己思考了,如果你已经把上面的内容仔细阅读,相信你会懂的其中的原理。

 

关于同步的话题,对于synchronized的解讲,就先告一段落了。接下来的章节将从JDK 1.5及以上版本提供的Lock进行解读。

 

 

 

Java多线程及线程池专题 汇总http://ciding.iteye.com/blog/1300110

4
2
分享到:
评论
2 楼 operthing 2012-04-17  
operthing 写道

1 楼 operthing 2012-04-17  

相关推荐

    Java多线程——线程八锁案例分析.docx

    在实际开发中,理解并熟练掌握这些线程同步机制对于构建高效且稳定的多线程应用程序至关重要。除了`synchronized`关键字,Java还提供了其他并发控制工具,如`java.util.concurrent`包下的`Semaphore`、`Lock`接口...

    JAVA多线程——一篇文章让你彻底征服多线程开发.docx

    1. **线程安全**:是指在多线程环境下,一个代码段即使被多个线程调用也不会出现错误的特性。一个典型的例子是在并发环境中进行转账操作时,如果没有适当的同步措施,可能会导致账户余额计算错误。 2. **同步**:是...

    多线程编程——实战篇

    接下来我们将通过一个具体的例子——“厨师-食客”模式来深入理解多线程编程的实际应用。 1. **模型介绍**: - 生产者(厨师):负责制作食物,并将其放入共享的“仓库”(桌子)。 - 消费者(食客):从“仓库”...

    java多线程教程——一个课件彻底搞清多线程

    本教程将深入讲解Java线程的相关知识,包括进程与线程的基本概念、线程的创建和启动、多线程的互斥与同步、线程状态和线程控制以及死锁的概念。 首先,我们要理解进程与线程的区别。进程是操作系统资源分配的基本...

    java 多线程同步方法的实例

    在Java编程语言中,多线程同步是一种控制多个线程并发执行的重要机制,它确保了共享资源的安全访问,防止数据不一致性和竞态条件的发生。本文将深入探讨Java中的多线程同步方法,并通过实例来阐述其工作原理。 首先...

    线程——基本线程的应用和线程调用控件

    - 线程安全的代码在多线程环境下不会产生错误或不一致的结果。 - 同步方法和同步块可以确保在同一时刻只有一个线程访问临界区。 - 使用原子操作或不可变对象也是保证线程安全的有效手段。 8. **线程池**: - 为...

    java多线程案例——未完成

    在这个未完成的案例中,我们可能正在探讨如何在Java中创建和管理线程,以及处理多线程环境下的并发问题。下面是对Java多线程基础知识的详细解释: 1. **线程的创建方式**: - 继承`Thread`类:自定义一个新的类,...

    操作系统实验 多线程同步与互斥 java编写 有界面

    操作系统实验是计算机科学教育中的重要组成部分,它帮助学生理解和掌握操作系统的基本原理,特别是多线程同步与互斥的概念。在Java编程环境下,这些概念可以通过实际的代码实现来深入理解。 多线程是现代操作系统中...

    文件复制——多线程

    在给定的标题“文件复制——多线程”中,我们可以推断出这个博客文章可能讨论的是如何利用多线程技术优化文件复制的过程。通常,这涉及到以下几个关键知识点: 1. **线程创建**:在Java或其他支持多线程的编程语言...

    Java多线程的小例子——吃包子

    这个名为"Java多线程的小例子——吃包子"的示例,旨在帮助开发者直观地理解多线程的工作原理。下面我们将深入探讨该示例所涉及的核心知识点。 首先,多线程通常涉及到以下几个关键概念: 1. **线程(Thread)**:...

    JAVA项目——多线程下载代码

    本项目以"JAVA项目——多线程下载代码"为主题,使用Eclipse集成开发环境进行实现,适合于Java初学者或毕业设计实践。下面我们将深入探讨相关的Java多线程下载知识点。 1. **线程基础**:在Java中,线程是程序执行的...

    Java多线程同步机制在售票系统的实现

    本文旨在通过分析一个具体的案例——长途汽车售票系统的多线程同步机制实现,来探讨如何利用Java的多线程技术解决实际问题。 #### 二、多线程的基本概念 ##### 2.1 线程与进程的主要区别 1. **划分粒度**:线程的...

    第13章龟兔赛跑——多线程.ppt

    总结来说,通过学习"第13章龟兔赛跑——多线程",我们可以掌握Java中多线程的基本概念、创建和管理,以及如何利用多线程提高程序效率。理解并熟练应用这些知识对于开发高效、响应迅速的软件至关重要。

    Java——多线程编程技术.pdf

    Java多线程编程技术的同步机制是通过Synchronized关键字和Volatile关键字来实现的。Synchronized关键字可以使线程之间同步执行,而Volatile关键字可以使线程之间共享变量。 Java多线程编程技术的应用场景非常广泛,...

    IOS应用源码——多线程.zip

    本资源“IOS应用源码——多线程.zip”包含了一个具体的iOS应用实例,用于展示如何在项目中实现多线程技术。通过分析这个源码,开发者可以深入理解多线程在iOS中的应用。 首先,我们要了解iOS中的多线程模型。iOS...

    Java练手小项目——多线程聊天室.zip

    2. volatile关键字:保证多线程环境下变量的可见性,防止指令重排序,但不保证原子性。 3. synchronized:用于解决并发访问同一资源的问题,提供互斥访问,确保同一时间只有一个线程执行特定代码块。 三、线程同步 ...

    面试题解惑系列(十)——话说多线程

    在多线程环境下,为了防止数据不一致性,Java提供了多种线程同步机制: 1. **synchronized**:用于方法或代码块,保证同一时刻只有一个线程访问被修饰的代码,实现互斥。 2. **wait()、notify()、notifyAll()**:...

    JAVA多线程同步机制及其应用.doc

    本文档详细介绍了Java中多线程的相关概念、创建方式、线程管理、同步机制以及一个实际的应用案例——交通灯管理系统。 首先,线程是程序执行的最小单元,一个进程可以包含多个线程。在Java中,创建线程有两种主要...

    JAVA内存模型——同步操作规则1

    它为多线程环境下如何保证数据一致性提供了理论基础。下面将详细阐述标题和描述中涉及的同步操作规则: 1. **顺序执行规则**: Read和Load操作必须先于Store和Write操作发生,这意味着在获取数据(Read/Load)之后...

    多线程面试题

    volatile确保了多线程环境下的变量可见性和有序性,但不保证原子性。 5. **死锁**:当两个或更多线程互相等待对方释放资源时,就会发生死锁。避免死锁的方法包括避免循环等待、设置超时、使用死锁检测算法等。 6. ...

Global site tag (gtag.js) - Google Analytics