【Java】利用synchronized(this)完成线程的临界区

在《【Java】线程并发、互斥与同步》(点击打开链接)中利用了操作系统通过操作信号量控制的原始方法,完成了线程的互斥与同步,说句题外话,其实这个信号量的算法,是著名的迪杰斯特拉创造的,也就是数据结构、计算机网络上面最短路径算法、迪杰斯特拉算法、Dijkstra算法的贡献人。其实Java里面根本就不需要自己定义一个信号量来实现临界区,Java对于临界区的实现早已封装好了,而且synchronized还是Java的关键字。

那么,到底怎么来使用这个关键字呢?下面就对上次《【Java】线程并发、互斥与同步》(点击打开链接)中的程序进行改进,不用信号量,同样来实行里面相应的功能。

一、基本目标

首先来回顾一下《【Java】线程并发、互斥与同步》(点击打开链接)中的程序,说的是有四个黄牛,分别是线程1、线程2、线程3、线程4,目的是要刷光票站里面的20张票,而且各自抢各自的票,不会出现多个黄牛抢一张票的情况,也就是说一个黄牛对应一张票

而且,每次运行结果不同:

这个程序上次是以信号量来实现的,下面就用利用synchronized(this)完成线程的临界区,在这里也就是买票的部分,只有一个售票窗口,每次只能容纳一个黄牛去抢票,而且这次每个线程都是以cpu的频率去刷票,刷2000只不虚,每次完成的结果不同,且一张票只落到一个黄牛的手里,

不会产生以下的无序、乱序的冲突:

甚至刷20000票都不虚:

由于程序是健壮的,你改到二十万张也是照样能跑:

每次运行结果不同是由于CPU分配的资源不同。

二、基本思想:

所谓的利用synchronized(this)完成线程的临界区,就是在一个implements Runnable的进程类里面有如下的一段代码,形成所谓的线程临界区:

至于许多不知道在写什么的操作系统,对于临界区是这样说的:每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。

其实,在这里,票就是临界资源,临界区就是黄牛如何买票。

三、制作过程

1、首先和《【Java】线程并发、互斥与同步》(点击打开链接)一样在主函数中开一个进程,其中这个进程里面有四个线程:

public class Threadtest {
	public static void main(String[] args) throws Exception {
		GetTicket getTicket = new GetTicket();
		new Thread(getTicket, "线程1").start();
		new Thread(getTicket, "线程2").start();
		new Thread(getTicket, "线程3").start();
		new Thread(getTicket, "线程4").start();
	}
}

2、这个进程类如下:

class GetTicket implements Runnable {
	//ticket是票数、isNotRead的设置是为了下面对于统计的输出只输出一次
	private int ticket = 200000;
	private boolean isNotRead = true;
	private int count1 = 0;
	private int count2 = 0;
	private int count3 = 0;
	private int count4 = 0;

	public void run() {
		while (this.ticket > 0) {
			//临界区开始,所谓的临界区就是仅能有一个线程访问的部分
			synchronized (this) {
				if (this.ticket > 0) {
					//对票的操作
					this.ticket--;
					System.out.println("票" + (200000 - this.ticket) + "被"
							+ Thread.currentThread().getName() + "买走,当前票数剩余:"
							+ this.ticket);
					//Thread.currentThread().getName()取当前线程的名字
					switch (Thread.currentThread().getName()) {
					case "线程1":
						this.count1++;
						break;
					case "线程2":
						this.count2++;
						break;
					case "线程3":
						this.count3++;
						break;
					case "线程4":
						this.count4++;
						break;
					}
				} else {
					//这4个线程无论怎么样都会经过这里的,所以为了只输出一次,必须设置一个布尔值
					//这个isNotRead某种程度上也是一个信号量
					if (isNotRead) {
						System.out.println("^_^票已卖光,明天请早,都各自散吧!(杀死所有进程)");
						System.out.println("=========得票统计=========");
						System.out.println("线程1:" + count1 + "张");
						System.out.println("线程2:" + count2 + "张");
						System.out.println("线程3:" + count3 + "张");
						System.out.println("线程4:" + count4 + "张");
						isNotRead = false;
					}
					//这段与Thread.currentThread.stop()等价,eclipse在JDK1.7中推荐这样写
					Thread.yield();
				}
			}
			//临界区结束
		}
	}
}

因此,整个进程如下:

class GetTicket implements Runnable {
	//ticket是票数、isNotRead的设置是为了下面对于统计的输出只输出一次
	private int ticket = 200000;
	private boolean isNotRead = true;
	private int count1 = 0;
	private int count2 = 0;
	private int count3 = 0;
	private int count4 = 0;

	public void run() {
		while (this.ticket > 0) {
			//临界区开始,所谓的临界区就是仅能有一个线程访问的部分
			synchronized (this) {
				if (this.ticket > 0) {
					//对票的操作
					this.ticket--;
					System.out.println("票" + (200000 - this.ticket) + "被"
							+ Thread.currentThread().getName() + "买走,当前票数剩余:"
							+ this.ticket);
					//Thread.currentThread().getName()取当前线程的名字
					switch (Thread.currentThread().getName()) {
					case "线程1":
						this.count1++;
						break;
					case "线程2":
						this.count2++;
						break;
					case "线程3":
						this.count3++;
						break;
					case "线程4":
						this.count4++;
						break;
					}
				} else {
					//这4个线程无论怎么样都会经过这里的,所以为了只输出一次,必须设置一个布尔值
					//这个isNotRead某种程度上也是一个信号量
					if (isNotRead) {
						System.out.println("^_^票已卖光,明天请早,都各自散吧!(杀死所有进程)");
						System.out.println("=========得票统计=========");
						System.out.println("线程1:" + count1 + "张");
						System.out.println("线程2:" + count2 + "张");
						System.out.println("线程3:" + count3 + "张");
						System.out.println("线程4:" + count4 + "张");
						isNotRead = false;
					}
					//这段与Thread.currentThread.stop()等价,eclipse在JDK1.7中推荐这样写
					Thread.yield();
				}
			}
			//临界区结束
		}
	}
}

public class Threadtest {
	public static void main(String[] args) throws Exception {
		GetTicket getTicket = new GetTicket();
		new Thread(getTicket, "线程1").start();
		new Thread(getTicket, "线程2").start();
		new Thread(getTicket, "线程3").start();
		new Thread(getTicket, "线程4").start();
	}
}

好了,既然临界区都怎么写,那么之后的生产者、消费者问题、读者写者问题、理发师问题、哲学家进餐问题,还是问题吗?就是来秀逗、开玩笑、送分的!都是一个道理,就是来看你,怎么处理好多个进程对同一资源提出请求时,你怎么安排好,避免出现以下的情况:

时间: 2024-10-28 04:34:04

【Java】利用synchronized(this)完成线程的临界区的相关文章

java线程 同步临界区:thinking in java4 21.3.5

thinking in java 4免费下载:http://download.csdn.net/detail/liangrui1988/7580155 package org.rui.thread.critical; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.uti

Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)

一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会引起此共享资源的不一致性.因此,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问. 线程安全问题多是由全局变量和静态变量引起的,当多个线程对共享数据只执行读操作,不执行写操作时,一般是线程安全的:当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题. 二.线程同步(synchr

JAVA技术专题综述之线程篇(1)

本文详细介绍JAVA技术专题综述之线程篇 编写具有多线程能力的程序经常会用到的方法有: run(),start(),wait(),notify(),notifyAll(),sleep(),yield(),join() 还有一个重要的关键字:synchronized 本文将对以上内容进行讲解. 一:run()和start() 示例1: public cla ThreadTest extends Thread{public void run(){for(int i=0;i<10;i++){Syste

java:synchronized 同步代码块

synchronized:利用上锁实现数据同步,避免多线程操作的情况下,数据出现异常. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行. 另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 在代码块前加上 synchronized关键字,则此代码块就成为同步代码块, 格式: synchronized(同步对象){ 需要同步的代码: } class MyThread implements Runnab

Java 理论和实践:线程池和工作队列

使用线程池以获取最佳资源利用率 Java 多线程编程论坛中最常见的一个问题就是各种版本的 "我怎么样才可以创建一个线程池?" 几乎在每个服务器应用里,都会出现关于线程池和工作队列的问题.本文中,Brian Goetz 就线程池原理.基本实现和调优技术.需要避开的一些常见误区等方面进行共享. 为何要用线程池? 有很多服务器应用,比如 Web 服务器,数据库服务器,文件服务器,或者邮件服务器,都会面对处理大量来自一些远程请求的小任务.请求可能会以很多种方式到达服务器,比如通过一种网络协议(

Java中synchronized关键字理解

好记性不如烂笔头~~ 并发编程中synchronized关键字的地位很重要,很多人都称它为重量级锁.利用synchronized实现同步的基础:Java中每一个对象都可以作为锁.具体表现为以下三种形式. (1)对于普通同步方法,锁是当前实例对象. (2)对于静态同步方法,锁是当前类的Class对象. (3)对于同步方法块,锁是synchronized括号里配置的对象. 一.普通同步方法 使用synchronized关键字修饰一个普通方法,锁住的是当前实例的对象.当synchronized锁住该对

java多线程总结五:线程池的原理及实现

1.线程池简介:     多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.        假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间.    如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能.                 一个线程池包括以下四个基本组成部分:                 1.线程池管理器(ThreadPool):用于创建并管

java多线程synchronized volatile解析

先简单说说原子性:具有原子性的操作被称为原子操作.原子操作在操作完毕之前不会线程调度器中断.即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行.在Java中,对除了long和double之外的基本类型的简单操作都具有原子性.简单操作就是赋值或者return.比如”a = 1;“和 “return a;”这样的操作都具有原子性.但是在Java中,类似”a += b”这样的操作不具有原子性,不是同步的就会出现难以预料的结果. 在我们平常的编程过程中,经常会遇到线程安

Java 的 synchronized

Java 的 synchronized 当任务要执行被syschronized方法时候,此对象都被加锁,这时该对象上其他synchronized方法只有等到前一个方法调用完毕并释放了锁才能被调用 class Demo{ synchronized void a(); syschronized void b(); } 如果a被调用并且没有被释放,此时调用b,是不能执行的,只有等待a执行完,才可以被调用 对应某个特定的对象来说,其所有synchronized方法共享了一个锁,这可以被用来防止多个任务同