`
liguanyi11111
  • 浏览: 62516 次
  • 性别: Icon_minigender_1
  • 来自: 黑龙江
社区版块
存档分类
最新评论

java线程同步初探

 
阅读更多

        线程是任何程序中都存在的相对独立的一段小程序。每个进程(程序)中都会含有许许多多的线程。少则只有一个多则成千上万。就例如一个班级,每个独立的人就是一个线程,他们既有自己独立的行为方式,也拥有共同的共享资源(教师,公告等等)。所以当很多学生同时阅读一份报告或者询问一个老师的时候就会有出现冲突。这时候就要有规矩,最简单的规矩就是排队。程序中也一样,不同的线程同时访问一片公有内存时,这种冲突无法避免。当然,如果是简单的阅读(只读)不会发生问题,但如果是修改其内容则就会出现悲剧。可以想象,当一个线程正在修改内存时另一个线程进入读取内容,显然会出现错误。这里用一个简单的例子来表示,现在火车的订票系统。

当一个用户查询余票时,发现还有票。准备购买,但购买的时间较长,票的数量还没有减少。此时,另一个用户查询余票,发现还有一张。但是在购买的时候,那张票已经被上一个用户购买了,实际上已经没有票了。这时就出现了错误。用一段代码来实现:

package 线程同步问题;

public class MyMain {	
	static int x=5; //总票数
	static final Object lock=new Object();
	public static void main(String[] args) {
		MyMain main=new MyMain();
		main.create();
	}
	public void create(){
		//5个人同时买票
		for(int i=1;i<=5;i++){
			TestThread tt=new TestThread(i);		
			tt.start();			
		}
	}
	//买票
	public static boolean buy(){
		//用于检查当前票数量是否符合规定
			if(x>0){
				System.out.println("购票成功");
				x--;
				return true;
			}else {
				return false;
			}
	}
	//线程,用来买票
	class TestThread extends Thread{
		int i;
		public TestThread(int i){
			this.i=i;
		}
		public void run(){
			boolean b=true;
			while(b){					
					b=MyMain.buy();
					System.out.println("第"+i+"个人购买后剩余="+MyMain.x);
				
			}
		}
	}
	
}

 结果:


     票的数量出现了负数,很明显,出现了上述错误。为了解决这个问题,我们就需要线程同步机制。说是同步,其实正相反,就是让线程排队。什么时候排队呢?当他们要同时使用(修改)一块内存时。我们使用关键字synchronize。一把锁。格式如下synchronize(object){ …… }

至于括号里的object就是这把锁的钥匙,可以使任何的object reference都可以当做钥匙。因为钥匙只是这个对象所对应的内存地址。只有当你得到了这片内存空间,你才能打开这把锁,执行锁中的代码。测试将上述代码修改为同步,如下:

//买票
	public static boolean buy(){
		synchronized(lock){
		//用于检查当前票数量是否符合规定
			if(x>0){
				System.out.println("购票成功");
				x--;
				return true;
			}else {
				return false;
			}
		}
	}

结果如下,可以明显的看出同步问题已解决:


       有些时候我们不一定只需要一把锁,因为有不同的线程访问不同的空间,发生同的冲突。我们就需要几把锁,把相应的需要进行同步的代码块进行相应的同步。例如

synchronize(lock1){ 代码1访问内存1   }  

synchronize(lock1){ 代码2访问内存1   }  

synchronize(lock2){ 代码3访问内存2   }   

synchronize(lock2){ 代码4访问内存2   }

这样来区分不同的锁与钥匙。

    这种同步模型只是最简单的模型,其实还有许多复杂的模型,这里就先介绍一下生产/消费者模型。其中也是用到了这种同步机制,但有了一个小小的改动,就是加入了wait/notify的使用。Waitnotify一个是等待通知,一个是发出通知。就是通过上述的“钥匙”调用wait方法进入等待队列Wait中,不执行后续代码。当有其他线程启用了notify的时候才继续执行。

等待通知:

synchronize(lock){

  lock.wait    放弃这个钥匙,使这个线程进入等待

   ……

}

这里的等待其实就是阻塞了这个线程。下面为测试代码

 

package 线程同步问题2;

public class MyMain {

	/**
	 * @param args
	 */
	static int x=0;
	static Object lock=new Object();
	public static void main(String[] args) {
		TestThread test1=new TestThread();
		TestThread2 test2=new TestThread2();
		TestThread3 test3=new TestThread3();
		test1.start();
		System.out.println("执行");
		test2.start();
		test3.start();
	}
	
	public static void add(){
		synchronized (lock) {
			try {
				lock.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			x++;
			System.out.println("add方法执行");
		}
		
	}
	
	public static void add2(){
		synchronized (lock) {
			try {
				lock.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			x++;
			System.out.println("add2方法执行");
		}
		
	}
	
	public static void add3(){
		synchronized (lock) {
			lock.notify();
			System.out.println("add3方法执行");
		}
		
	}
}

 

 

package 线程同步问题2;

public class TestThread3 extends Thread{
	int count=0;
	public void run(){
		while(true){
			count++;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("count="+count);
			if(count%5==0){
				MyMain.add3();
			}
		}
	}
	
}

 

package 线程同步问题2;

public class TestThread2 extends Thread{
	
	public void run(){
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			MyMain.add2();
			
		}
	}
}

 

package 线程同步问题2;

public class TestThread extends Thread{
	public void run(){
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			MyMain.add();
		}
	}
	
}

 测试结果如下:



 有此结果可得出如下结论:

1.当一个线程放弃钥匙进入等待时,其他线程可以获取钥匙执行代码。

2.进入等待的线程会在等待队列中排队等待通知。

3.当一个线程发出notify通知时,在等待队列中的靠前的代码会先执行,并且只执行一个。其他的继续等待通知。

4.发出通知这一举动不会影响本线程的执行。

ps:notigyAll可以通知全部线程。  

ps2:wait()中可以加入等待时间,时间已到则无需通知直接进入执行队列ready。

ps3:获取到通知或者时间到后(即已进入执行队列中ready),仍需要先竞争到钥匙才可以继续执行代码。

本次的同步问题暂时就先说到这里,如果再有其他心得会继续更新。。。

  • 大小: 18.9 KB
  • 大小: 27.2 KB
  • 大小: 79.4 KB
分享到:
评论

相关推荐

    基于计算机软件开发的JAVA编程应用初探.pdf

    Java的多线程支持也涵盖了线程同步机制,这有助于防止线程之间的数据冲突和资源竞争,保证了程序执行的正确性和稳定性。 在实际应用中,Java语言通过其标准库提供了丰富的类和接口,覆盖了包括网络通信、数据库访问...

    Java多线程编程深入详解.docx

    Java的多线程模型基于线程优先级、线程同步和线程通信等机制。 第一个多线程程序 创建一个简单的多线程程序,继承Thread类, 覆写run方法,使用start方法启动线程。需要注意的是,线程的执行顺序是不可预测的,...

    计算机软件开发中JAVA编程语言及其实际应用.pdf

    创建Java线程有多种方式,其中最常用的是继承Thread类和实现Runnable接口。多线程的实现能够将一个大任务分解为多个小任务,并行处理,这样可以充分利用计算资源,达到更快的执行速度。在并发编程中,线程之间共享...

    Java-program-design-.rar_Java 8

    这一章会讲解线程的概念、创建线程的方法(如继承Thread类和实现Runnable接口),以及线程同步机制,如synchronized关键字、wait()、notify()和notifyAll()方法。还会介绍Java 8引入的Lambda表达式和Stream API在...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    JVM初探- 内存分配、GC原理与垃圾收集器

    为了减少内存分配时的线程同步问题,JVM引入了本地线程分配缓冲(TLAB),允许每个线程在TLAB上分配对象。只有在TLAB用尽或者对象过大无法在TLAB上分配时,才会直接在Eden区分配。另外,JVM还提供了参数控制大对象...

    Android 线程池、信号量、Looper、缓存初探

    这时,我们可以通过创建一个自定义Looper,或者使用HandlerThread,使后台线程拥有处理消息的能力,从而实现异步操作与UI更新的同步。 最后是缓存,它是提高应用性能的重要手段。Android提供了多种缓存策略,如LRU...

    bbosspersistent 性能初探

    bbosspersistent可能会使用线程安全的数据结构和同步机制来确保在并发环境下的正确性。例如,它可能使用了Java的`synchronized`关键字或者`ReentrantLock`等高级锁来控制并发访问。 此外,bbosspersistent的SQL执行...

    C语言文件操作、多线程编程和网络编程.md

    - **3.2 创建与同步线程** `pthread_create`函数用于创建新的线程。`pthread_join`函数则用于等待指定线程结束。这两个函数是多线程编程中最基本的操作。 #### 4. 网络编程基础 - **4.1 套接字的概念与创建** ...

    RxAndroidDemo:rxjava初探

    通常,`subscribeOn(Schedulers.io())`将操作放到IO线程,`observeOn(AndroidSchedulers.mainThread())`则确保结果在UI线程中处理,避免了线程同步的问题。 4. **生命周期管理**: Android应用有多种生命周期状态...

    Keyboard-Hero:我 11 年级计算机科学课的最后一个项目。 没什么特别的,但这是我第一次接触到 Java 的 GUI(程序是用 NetBeans 制作的)

    《Java GUI编程初探:以Keyboard-Hero项目为例》 在计算机科学的学习过程中,实践项目总是能给我们带来深刻的体验和宝贵的知识。本文将以"Keyboard-Hero"项目为例,深入探讨11年级学生初次接触Java GUI编程时可能...

    Retrofit 源码分析初探

    在这里,我们使用的是异步执行 `enqueue` 方法,它会在后台线程执行网络请求,并在主线程回调结果。 总结一下,Retrofit 的主要工作流程是: 1. 初始化 Retrofit 实例,配置基础 URL 和转换器。 2. 定义接口,使用...

    brandonsladek:我最早写的一些程序

    【 Brandon Sladek 的编程之旅:Java初探】 在编程世界中,每个程序员都有自己的起点,布兰登·斯拉德克(Brandon Sladek)也不例外。他的编程旅程始于他编写的一些早期程序,这些程序被收集在一个名为...

    Songs_Player:添加了初始的Simple Songs Player应用程序

    这需要正确处理线程同步和通信,避免出现线程安全问题。 7. **错误处理和日志记录**:为了提高软件的稳定性和可维护性,开发者可能会添加异常处理和日志记录功能,以便在出现问题时能够快速定位和修复。 通过对这...

    ProyectoNomina:我的第一个工资系统的个人项目

    《构建个人工资系统:ProyectoNomina初探》 在信息技术领域,个人项目是提升技能、展示才华的重要途径。本文将深入探讨一个名为"ProyectoNomina"的个人项目,这是一个由APEC大学技术应用学科的学生在2013年12月完成...

    SE2_Einzelphase

    9. **多线程**:学习如何创建和管理线程,使用synchronized关键字进行同步,以及理解Thread和Runnable接口的区别。 10. **网络编程**:探索Socket编程,理解TCP和UDP协议,以及如何创建简单的客户端和服务端应用...

    Physnetest:首次尝试使用网络多人游戏进行基于物理的游戏

    《Physnetest: 利用Java打造的网络多人物理游戏初探》 在当今的数字娱乐领域,网络多人游戏已经成为了一种主流趋势,它将玩家从单机游戏的局限中解放出来,允许他们与全球各地的玩家进行实时互动。而《Physnetest》...

    安卓开发技巧总汇

    键盘响应函数可以捕捉用户键入事件,而线程处理则涉及到任务调度、同步和异步通信,以提高应用性能和响应速度。 #### MessageHandler监听消息与显示提示信息 MessageHandler是Android中用于线程间通信的重要机制,...

    MovingGame:第一个安卓游戏

    《MovingGame:初探安卓游戏开发》 在数字化娱乐的时代,移动游戏已成为众多开发者和玩家关注的焦点。本文将深入探讨“MovingGame”,一个由WyTiny设计的基于Android平台的球类游戏,以此来揭示安卓游戏开发的基础...

Global site tag (gtag.js) - Google Analytics