Java 多线程详解(三)------线程的同步

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html

Java 多线程详解(二)------如何创建进程和线程:http://www.cnblogs.com/ysocean/p/6883491.html

介绍完如何创建进程以及线程了,那么我们接着来看一个实例:

  利用多线程模拟 3 个窗口卖票

第一种方法:继承 Thread 类

 创建窗口类 TicketSell 

package com.ys.thread;

public class TicketSell extends Thread{
	//定义一共有 50 张票,注意声明为 static,表示几个窗口共享
	private static int num = 50;

	//调用父类构造方法,给线程命名
	public TicketSell(String string) {
		super(string);
	}
	@Override
	public void run() {
		//票分 50 次卖完
		for(int i = 0 ; i < 50 ;i ++){
			if(num > 0){
				try {
					sleep(10);//模拟卖票需要一定的时间
				} catch (InterruptedException e) {
					// 由于父类的 run()方法没有抛出任何异常,根据继承的原则,子类抛出的异常不能大于父类, 故我们这里也不能抛出异常
					e.printStackTrace();
				}
				System.out.println(this.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
			}
		}
	}

}

  创建主线程测试:

package com.ys.thread;

public class TestTicket {

	public static void main(String[] args) {
		//创建 3 个窗口
		TicketSell t1 = new TicketSell("A窗口");
		TicketSell t2 = new TicketSell("B窗口");
		TicketSell t3 = new TicketSell("C窗口");

		//启动 3 个窗口进行买票
		t1.start();
		t2.start();
		t3.start();
	}
}

  结果:这里我们省略了一些,根据电脑配置,结果会随机出现不同

B窗口卖出一张票,剩余48张
A窗口卖出一张票,剩余47张
C窗口卖出一张票,剩余49张
C窗口卖出一张票,剩余46张
B窗口卖出一张票,剩余44张
A窗口卖出一张票,剩余45张
A窗口卖出一张票,剩余43张
...
C窗口卖出一张票,剩余5张
A窗口卖出一张票,剩余4张
B窗口卖出一张票,剩余3张
A窗口卖出一张票,剩余2张
C窗口卖出一张票,剩余3张
B窗口卖出一张票,剩余1张
C窗口卖出一张票,剩余0张
A窗口卖出一张票,剩余-1张

  

第二种方法:实现 Runnable 接口

  创建窗口类 TicketSellRunnable

package com.ys.thread;

public class TicketSellRunnable implements Runnable{

	//定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
	private int num = 50;

	@Override
	public void run() {
		//票分 50 次卖完
		for(int i = 0 ; i < 50 ;i ++){
			if(num > 0){
				try {
					//模拟卖一次票所需时间
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
			}
		}
	}

}

  创建主线程测试:

package com.ys.thread;

public class TicketSellRunnableTest {
	public static void main(String[] args) {
		TicketSellRunnable t = new TicketSellRunnable();

		Thread t1 = new Thread(t,"A窗口");
		Thread t2 = new Thread(t,"B窗口");
		Thread t3 = new Thread(t,"C窗口");

		t1.start();
		t2.start();
		t3.start();
	}

}

  结果:同理为了篇幅我们也省略了中间的一些结果

B窗口卖出一张票,剩余49张
C窗口卖出一张票,剩余48张
A窗口卖出一张票,剩余49张
B窗口卖出一张票,剩余47张
A窗口卖出一张票,剩余45张
......
A窗口卖出一张票,剩余4张
C窗口卖出一张票,剩余5张
A窗口卖出一张票,剩余3张
B窗口卖出一张票,剩余2张
C窗口卖出一张票,剩余1张
B窗口卖出一张票,剩余0张
A窗口卖出一张票,剩余-2张
C窗口卖出一张票,剩余-1张

  

结果分析:这里出现了票数为 负数的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?

  

解决办法分析:即我们不能同时让超过两个以上的线程进入到 if(num>0)的代码块中,不然就会出现上述的错误。我们可以通过以下三个办法来解决:

1、使用 同步代码块

2、使用 同步方法

3、使用 锁机制

①、使用同步代码块

语法:
synchronized (同步锁) {
    //需要同步操作的代码
}

同步锁:为了保证每个线程都能正常的执行原子操作,Java 线程引进了同步机制;同步锁也叫同步监听对象、同步监听器、互斥锁;
Java程序运行使用的任何对象都可以作为同步监听对象,但是一般我们把当前并发访问的共同资源作为同步监听对象

注意:同步锁一定要保证是确定的,不能相对于线程是变化的对象;任何时候,最多允许一个线程拿到同步锁,谁拿到锁谁进入代码块,而其他的线程只能在外面等着

  实例:

public void run() {
		//票分 50 次卖完
		for(int i = 0 ; i < 50 ;i ++){
			//这里我们使用当前对象的字节码对象作为同步锁
			synchronized (this.getClass()) {
				if(num > 0){
					try {
						//模拟卖一次票所需时间
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
				}
			}

		}
	}

  

2、使用 同步方法

语法:即用  synchronized  关键字修饰方法

@Override
	public void run() {
		//票分 50 次卖完
		for(int i = 0 ; i < 50 ;i ++){
			sell();

		}
	}
	private synchronized void sell(){
		if(num > 0){
			try {
				//模拟卖一次票所需时间
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
		}
	}

  注意:不能直接用 synchronized 来修饰 run() 方法,因为如果这样做,那么就会总是第一个线程进入其中,而这个线程执行完所有操作,即卖完所有票了才会出来。

3、使用 锁机制

  

public interface Lock

  主要方法:

  常用实现类:

public class ReentrantLock
extends Object
implements Lock, Serializable//一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

  例子:

package com.ys.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketSellRunnable implements Runnable{

	//定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
	private int num = 50;
	//创建一个锁对象
	Lock l = new ReentrantLock();

	@Override
	public void run() {
		//票分 50 次卖完
		for(int i = 0 ; i < 50 ;i ++){
			//获取锁
			l.lock();
			try {
				if(num > 0){
				//模拟卖一次票所需时间
				Thread.sleep(10);
				System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+(--num)+"张");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				//释放锁
				l.unlock();
			}

		}
	}
	private void sell(){

	}

}

  

  

时间: 2024-12-30 00:05:53

Java 多线程详解(三)------线程的同步的相关文章

java多线程详解(6)-线程间的通信wait及notify方法

Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New):线程对象已经产生,但尚未被启动,所以无法执行.如通过new产生了一个线程对象后没对它调用start()函数之前.(2). 可执行(Runnable):每个支持多线程的系统都有一个排程器,排程器会从线程池中选择一个线程并启动它. 当一个线程处于可执行状态时,表示它可能正处于线程池中等待排排程器启动它

java多线程详解(7)-线程池的使用

在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, 这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 这个是时候我们需要使用线程池技术创建多线程. 本文目录大纲: 一.Java中的ThreadPoolExecutor类 二.深入剖析线程池实现原理 三.使用示例 四.如何合理配置线程池的大小 一.Java中的ThreadPoolExecutor类 jav

Java 多线程详解(五)------线程的声明周期

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程:http://www.cnblogs.com/ysocean/p/6883491.html Java 多线程详解(三)------线程的同步:http://www.cnblogs.com/ysocean/p/6883729.html Java 多线程详解(四)------生产者和消费者:http:/

Java 多线程详解(二)------如何创建进程和线程

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html 在上一篇博客中,我们已经介绍了并发和并行的区别,以及进程和线程的理解,那么在Java 中如何创建进程和线程呢? 1.在 Windows 操作系统中创建进程 在 windows 操作系统中,我们创建一个进程通常就是打开某个应用软件,这便在电脑中创建了一个进程.更原始一点的,我们在命令提示符中来做(我们以打开记事本这个进程为例): 第一步:windows+R,

java多线程详解

转自:线程间通信.等待唤醒机制.生产者消费者问题(Lock,Condition).停止线程和守护线程.线程优先级 1  线程间通信 1.1  线程间通信 其实就是多个线程在操作同一个资源,但是操作的动作不同. 比如一个线程给一个变量赋值,而另一个线程打印这个变量. 1.2  等待唤醒机制 wait():将线程等待,释放了CPU执行权,同时将线程对象存储到线程池中. notify():唤醒线程池中一个等待的线程,若线程池有多个等待的线程,则任意唤醒一个. notifyAll():唤醒线程池中,所有

Java多线程详解与晋级(一)

Java多线程详解与晋级(一) Java线程的概念与原理 一.操作系统中线程和进程的概念 进程是指操作系统内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程. 线程是指进程中的一个执行流程,一个进程中可以运行多个线程.比如java.exe进程中可以运行很多线程.线程总是属于某个进程,进程中的多个线程共享进程的内存. 简单来说,进程就是一个应用程序在操作系统上的一次执行过程,而线程是进程中的一次执行流程,进程包含多个线程在运行. 二.Java的线程 Thread类

spark2.x由浅入深深到底系列六之RDD java api详解三

学习任何spark知识点之前请先正确理解spark,可以参考:正确理解spark 本文详细介绍了spark key-value类型的rdd java api 一.key-value类型的RDD的创建方式 1.sparkContext.parallelizePairs JavaPairRDD<String, Integer> javaPairRDD =         sc.parallelizePairs(Arrays.asList(new Tuple2("test", 3

Java多线程详解(二)

评论区留下邮箱可获得<Java多线程设计模式详解> 转载请指明来源 1)后台线程 后台线程是为其他线程服务的一种线程,像JVM的垃圾回收线程就是一种后台线程.后台线程总是等到非后台线程死亡之后,后台线程没有了服务对象,不久就会自动死亡,不再复活.利用setDaemon方法可以把一个线程设置为后台线程,但必须在线程启动之前调用. 例如 : /* * @author [email protected] */ public class DaemonThread extends Thread { pu

原创Java多线程详解(一)

只看书不实践是不行的.来实践一下~~~~~~(引用请指明来源) 先看看百科对多线程的介绍 http://baike.baidu.com/view/65706.htm?fr=aladdin Java对多线程的支持 Java创建多线程的3种常用方法: 1)继承Thread类 重写Thread类的run方法,创建Thread子类实例,启动线程. 例如: /* * @author [email protected] wangxu */ public class TreadOfextends extend