I学霸官方免费教程四十 :Java基础教程之线程同步

线程的同步

指当多个线程使用同一对象中被同步的资源时,要根据“先来后到”的顺序使用。
举个例子:现在只有一台电脑,现在有两个人A和B想玩游戏,一个人C想写代码,一个人D想听音乐。此时A、B、C三个人要抢这台电脑,谁先抢到谁用,用完了后面两个人在接着抢,谁抢到谁用。而D则不用,在另外三个人中任意一个人正在使用的时候,都可以播放音乐给他听;由此可以看出玩游戏和写代码的功能(方法)是要有“先来后到”的顺序的,而听音乐这个功能不需要。所以玩游戏和写代码的方法就是需要被同步的,而听音乐就不需要同步
同步使用关键字synchronized实现
同步方法:[访问修饰符]  synchronized  返回值类型  方法名(){}
同步块:synchronized(对象) { //代码块 }
同步块和前面所提到的代码块一样,只不过这块代码是被同步的。被同步的部分也按照“先来后到”的顺序执行
在开发的过程中,应当尽量缩小同步代码的范围,因为多个线程执行时要有顺序的执行,这样会大大降低程序的运行效率

以下实例代码中创建了一个Computer类中含有一个非同步的方法listenMusic,一个含有同步块的非同步方法printer和两个同步方法playGame和coding。
七个线程类ThreadA、ThreadB、ThreadC、ThreadD、ThreadE、ThreadF、ThreadG;其中ThreadA和ThreadB中都执行了非同步的方法listenMusic;ThreadC和ThreadD中都执行了同步方法playGame;ThreadE中执行了同步方法coding;ThreadF中ThreadG都执行了含有同步块的非同步方法printer;
五个测试类TestAB、TestBC、TestCD、TestDE、TestEF、TestFG;根据测试类的类名最后两个字母,测试对象的两个线程;例如TestAB测试ThreadA和ThreadB

实例:
package thread.synchronize;
/**
 * 创建Computer类
 * 其中包含同步方法,非同步方法和含有同步块的非同步方法
 * @author 学霸联盟 - 赵灿
 */
public class Computer {
	/**
	 * 非同步方法;功能:听音乐
	 * @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
	 */
	public void listenMusic(String threadTag) {
		System.out.println(threadTag + "-听音乐开始");
		try {
			//此处休眠1秒是为了模拟编写代码消耗的时间
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(threadTag + "-听音乐结束");
	}
	/**
	 * 含有同步块的非同步方法;功能:打印材料
	 * @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
	 */
	public void printer(String threadTag) {
		System.out.println(threadTag + "-准备材料开始");
		try {
			//此处休眠1秒是为了模拟准备材料消耗的时间
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(threadTag + "-准备材料结束");
		//同步块:获得当前对象的锁
		synchronized(this){
			System.out.println(threadTag + "-打印材料开始");
			try {
				//此处休眠1秒是为了模拟打印材料消耗的时间
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(threadTag + "-打印材料结束");
		}
	}
	/**
	 * 同步方法;功能:玩游戏
	 * @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
	 */
	public synchronized void playGame(String threadTag) {
		System.out.println(threadTag + "-玩游戏开始");
		try {
			//此处休眠3秒是为了模拟玩游戏消耗的时间
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(threadTag + "-玩游戏结束");
	}
	/**
	 * 同步方法;功能:编写代码
	 * @param threadTag:线程标记,用于标识哪个线程正在执行这个方法
	 */
	public synchronized void coding(String threadTag) {
		System.out.println(threadTag + "-编写代码开始");
		try {
			//此处休眠3秒是为了模拟编写代码消耗的时间
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(threadTag + "-编写代码结束");
	}
}

package thread.synchronize;
/**
 * 创建ThreadA类
 * 用于执行Computer对象中的非同步方法listenMusic
 * @author 学霸联盟 - 赵灿
 */
public class ThreadA extends Thread {
	/*
	 * 声明成员变量pc
	 * 目的是因为在run方法中需要使用外部传进来的Computer对象
	 * 但run方法中无法使用构造方法中的局部变量localPC
	 * 所以此处声明一个成员变量pc用于接收外部传入的对象
	 * 这样以来run方法中,便可以通过成员变量pc可以使用外部传入的对象了
	 */
	private Computer pc;
	/*
	 * 此处的localPC会接收到外部传入的Computer类型的对象
	 * 并将其传递给成员变量pc
	 */
	public ThreadA(Computer localPC) {
		pc = localPC;
	}
	@Override
	public void run() {
		//使用pc完成听音乐的更能,即使用pc调用listenMusic方法
		pc.listenMusic("ThreadA");
	}
}

package thread.synchronize;
/**
 * 创建ThreadB类:作用和代码同ThreadA
 * @author 学霸联盟 - 赵灿
 */
public class ThreadB extends Thread {
	private Computer pc;
	public ThreadB(Computer localPC) {
		pc = localPC;
	}
	@Override
	public void run() {
		pc.listenMusic("ThreadB");
	}
}

package thread.synchronize;
/**
 * 创建测试类TestAB
 * 用于测试多个线程同时调用同一对象的同一个非同步方法
 * 结果将是多个线程同时执行非同步方法
 * @author 学霸联盟 - 赵灿
 */
public  class  TestAB {
	public  static  void  main(String[] args) {
		/**
		 * 为了保证只有一台电脑(即同一个对象)供多个线程访问
		 * 所以只在此处创建一个Computer对象作为实参传递给各个线程
		 */
		Computer  pc = new  Computer();
		//ThreadA和ThreadB中都执行了pc的非同步方法listenMusic
		ThreadA  ta = new  ThreadA(pc);
		ThreadB  tb = new  ThreadB(pc);
		//启动线程ta和tb
		ta.start();
		tb.start();
	}
}
运行结果:
ThreadA-听音乐开始
ThreadB-听音乐开始
ThreadB-听音乐结束
ThreadA-听音乐结束
package thread.synchronize;
/**
 * 创建ThreadC类:代码基本和ThreadA相同
 * @author 学霸联盟 - 赵灿
 */
public class ThreadC extends Thread {
	private Computer pc;
	public ThreadC(Computer localPC) {
		pc = localPC;
	}
	@Override
	public void run() {
		//调用pc的同步方法playGame
		pc.playGame("ThreadC");
	}
}

package thread.synchronize;
/**
 * 创建测试类TestBC
 * 用于测试两个线程同时调用同一对象的非同步方法和同步方法
 * 结果是执行非同步方法和同步方法的两个线程可以同时执行,互不影响
 * @author 学霸联盟 - 赵灿
 */
public  class  TestBC {
	public  static  void  main(String[] args) {
		/**
		 * 为了保证只有一台电脑(即同一个对象)供多个线程访问
		 * 所以只在此处创建一个Computer对象作为实参传递给各个线程
		 */
		Computer  pc = new  Computer();
		//ThreadB执行了pc的非同步方法listenMusic
		ThreadB  tb = new  ThreadB(pc);
		//ThreadC执行了pc的同步方法playGame
		ThreadC  tc = new  ThreadC(pc);
		tb.start();
		tc.start();
	}
}
运行结果:
ThreadC-玩游戏开始
ThreadB-听音乐开始
ThreadB-听音乐结束
ThreadC-玩游戏结束
package thread.synchronize;
/**
 * 创建ThreadD类:代码和功能同ThreadC
 * @author 学霸联盟 - 赵灿
 */
public class ThreadD extends Thread {
	private Computer pc;
	public ThreadD(Computer localPC) {
		pc = localPC;
	}
	@Override
	public void run() {
		//调用pc的同步方法playGame
		pc.playGame("ThreadD");
	}
}

package thread.synchronize;
/**
 * 创建测试类TestCD
 * 用于测试多个线程同时调用同一对象的同一个同步方法
 * 结果是某一线程先获得对象的锁的执行完后,第二个线程才能执行
 * @author 学霸联盟 - 赵灿
 */
public  class  TestCD {
	public  static  void  main(String[] args) {
		/**
		 * 为了保证只有一台电脑(即同一个对象)供多个线程访问
		 * 所以只在此处创建一个Computer对象作为实参传递给各个线程
		 */
		Computer  pc = new  Computer();
		//ThreadC和ThreadD中都执行了pc的同步方法playGame
		ThreadC  tc = new  ThreadC(pc);
		ThreadD  td = new  ThreadD(pc);
		//启动线程tc和td
		tc.start();
		td.start();
	}
}
运行结果:
ThreadC-玩游戏开始
ThreadC-玩游戏结束
ThreadD-玩游戏开始
ThreadD-玩游戏结束
package thread.synchronize;
/**
 * 创建ThreadE类
 * @author 学霸联盟 - 赵灿
 */
public class ThreadE extends Thread {
	private Computer pc;
	public ThreadE(Computer localPC) {
		pc = localPC;
	}
	@Override
	public void run() {
		//调用pc的同步方法coding
		pc.coding("ThreadE");
	}
}

package thread.synchronize;
/**
 * 创建测试类TestDE
 * 用于测试多个线程同时调用同一对象的不同的同步方法
 * 结果也是某一线程先获得对象的锁的执行完后,第二个线程才能执行
 * @author 学霸联盟 - 赵灿
 */
public  class  TestDE {
	public  static  void  main(String[] args) {
		/**
		 * 为了保证只有一台电脑(即同一个对象)供多个线程访问
		 * 所以只在此处创建一个Computer对象作为实参传递给各个线程
		 */
		Computer  pc = new  Computer();
		//ThreadD执行了pc的同步方法playGame
		ThreadD  td = new  ThreadD(pc);
		//ThreadE中执行了pc的同步方法coding
		ThreadE  te = new  ThreadE(pc);
		td.start();
		te.start();
	}
}
运行结果:
ThreadD-玩游戏开始
ThreadD-玩游戏结束
ThreadE-编写代码开始
ThreadE-编写代码结束
package thread.synchronize;
/**
 * 创建ThreadF类
 * @author 学霸联盟 - 赵灿
 */
public class ThreadF extends Thread {
	private Computer pc;
	public ThreadF(Computer localPC) {
		pc = localPC;
	}
	@Override
	public void run() {
		//调用pc的含同步块的方法printer
		pc.printer("ThreadF");
	}
}

package thread.synchronize;
/**
 * 创建测试类TestEF
 * 用于测试多个线程同时调用同一对象的同步方法和含有同步块的非同步方法
 * 结果将是被同步的部分有先后,非同步部分代码同时执行
 * @author 学霸联盟 - 赵灿
 */
public  class  TestEF {
	public  static  void  main(String[] args) {
		/**
		 * 为了保证只有一台电脑(即同一个对象)供多个线程访问
		 * 所以只在此处创建一个Computer对象作为实参传递给各个线程
		 */
		Computer  pc = new  Computer();
		//ThreadE中执行了pc的同步方法coding
		ThreadE  te = new  ThreadE(pc);
		//ThreadF执行了pc的含同步块的非同步方法printer
		ThreadF  tf = new  ThreadF(pc);
		te.start();
		tf.start();
	}
}
运行结果:
ThreadF-准备材料开始
ThreadE-编写代码开始
ThreadF-准备材料结束
ThreadE-编写代码结束
ThreadF-打印材料开始
ThreadF-打印材料结束
package thread.synchronize;
/**
 * 创建ThreadG类
 * @author 学霸联盟 - 赵灿
 */
public class ThreadG extends Thread {
	private Computer pc;
	public ThreadG(Computer localPC) {
		pc = localPC;
	}
	@Override
	public void run() {
		//调用pc的含同步块的方法printer
		pc.printer("ThreadG");
	}
}
package thread.synchronize;

/**
 * 创建测试类TestFG
 * 用于测试多个线程同时调用同一对象中同一个含有同步块的非同步方法
 * 结果将是被同步的部分有先后,非同步部分代码同时执行
 * @author 学霸联盟 - 赵灿
 */
public  class  TestFG {
	public  static  void  main(String[] args) {
		/**
		 * 为了保证只有一台电脑(即同一个对象)供多个线程访问
		 * 所以只在此处创建一个Computer对象作为实参传递给各个线程
		 */
		Computer  pc = new  Computer();
		//ThreadF和ThreadG都执行了pc的含同步块的非同步方法printer
		ThreadF  tf = new  ThreadF(pc);
		ThreadG  tg = new  ThreadG(pc);
		tf.start();
		tg.start();
	}
}
运行结果:
ThreadF-准备材料开始
ThreadG-准备材料开始
ThreadG-准备材料结束
ThreadG-打印材料开始
ThreadF-准备材料结束
ThreadG-打印材料结束
ThreadF-打印材料开始
ThreadF-打印材料结束

wait方法和notify方法/notifyAll方法

wait([int][,int]); Object类中的方法,在线程A中使用对象o调用wait方法;会阻塞线程A;
如果没有传入时间参数,直到在其他地方调用对象o的notify/notifyAll时才会恢复;
如果传入long类型的时间参数,指定时间结束后自动恢复
而且线程A必须先获得对象o的锁,否则会出现异常。

实例:
package thread.wait;
/**
* 创建WaitDemo类
* 用于测试wait、notify、notifyAll方法
* @author 学霸联盟 - 赵灿
*/
public class WaitDemo {
	public static void main(String[] args) {
		/*
		 * 在这里创建一个唯一的Object对象onlyObject
		 * 用于传递给以下三个线程使用,是为了保证三个线程使用的是同一对象
		 */
		Object onlyObject = new Object();
		/*
		 * 创建三个线程对象ta、tb、tc
		 * onlyObject会被赋值给每个线程的构造方法中的变量localObject
		 * 相当于localObject = onlyObject;
		 */
		ThreadA ta = new ThreadA(onlyObject);
		ThreadB tb = new ThreadB(onlyObject);
		ThreadC tc = new ThreadC(onlyObject);
		//先启动线程ta和tb
		ta.start();
		tb.start();
		try {
			/*
			 * 此处执行Thread.sleep(100);会是主线程休眠100毫秒
			 * 100毫秒后才会执行tc.start();启动线程tc
			 * 目的是为了保证线程ta和tb中的wait方法先执行,阻塞住线程ta和tb
			 * 然后在执行线程tc中的notify()方法是唤醒操作
			 * 否则如果先执行了唤醒操作,而后执行阻塞操作,将看不到唤醒的效果
			 */
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//启动线程tc
		tc.start();
	}
}

/**
* 创建ThreadA类
* 用于执行wait方法;阻塞操作
* @author 学霸联盟 - 赵灿
*/
class ThreadA extends Thread {
	/*
	 * 声明成员变量obj
	 * 目的是因为在run方法中需要使用外部传进来的Object对象
	 * 但run方法中无法使用构造方法中的局部变量localObject
	 * 所以此处声明一个成员变量obj用于接收外部传入的对象
	 * 这样以来run方法中,便可以通过成员变量obj可以使用外部传入的对象了
	 */
	private Object obj;
	/*
	 * 此处的localObject会接收到外部传入的Object类型的对象
	 * 并将其传递给成员变量obj
	 */
	public ThreadA(Object localObject) {
		//赋值给成员变量obj
		this.obj = localObject;
	}

	@Override
	public void run() {
		System.out.println("ThreadA--执行开始");
		/*
		 * 调用wait方法时,必须使用同步的方式获取调用wait方法对象的锁
		 * 否则会出现IllegalMonitorStateException异常
		 * 本例中,此处使用的是obj调用wait(obj.wait())
		 * 所以同步的也应该是obj(synchronized(obj) )
		 */
		synchronized (obj) {
			try {
				System.out.println("ThreadA--被阻塞");
				/*
				 * 调用对象obj的wait方法,阻塞的将是执行这句代码的线程
				 * 本例中是执行此处代码的是线程ta,所以被阻塞的就是线程ta
				 */
				obj.wait();
				System.out.println("ThreadA--被唤醒");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("ThreadA--执行结束");
	}
}

/**
* 创建ThreadB类
* 用于执行wait方法;阻塞操作
* 其中的代码和ThreadA相同,就不再一一写注释了
* @author 学霸联盟 - 赵灿
*/
class ThreadB extends Thread {
	private Object obj;
	public ThreadB(Object localObject) {
		this.obj = localObject;
	}
	@Override
	public void run() {
		synchronized (obj) {
			System.out.println("ThreadB--执行开始");
			synchronized (obj) {
				try {
					System.out.println("ThreadB--被阻塞");
					/*
					 * 执行obj.wait(),阻塞的将是执行这句代码的线程
					 * 本例中是执行此处代码的是线程tb,所以被阻塞就是线程tb
					 */
					obj.wait();
					System.out.println("ThreadB--被唤醒");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("ThreadB--执行结束");
		}
	}
}

/**
* 创建ThreadC类
* 用于执行notify方法;唤醒操作
* @author 学霸联盟 - 赵灿
*/
class ThreadC extends Thread {
	private Object obj;

	public ThreadC(Object localObject) {
		this.obj = localObject;
	}

	@Override
	public void run() {
		/*
		 * 调用notify方法时,必须使用同步的方式获取调用notify方法对象的锁
		 * 否则会出现IllegalMonitorStateException异常
		 * 本例中,此处使用的是obj调用notify(obj.notify())
		 * 所以同步的也应该是obj(synchronized(obj) )
		 */
		synchronized (obj) {
			System.out.println("ThreadC--唤醒最先被obj对象阻塞的线程");
			/*
			 * 执行obj.notify();将唤醒第一个被obj阻塞的线程
			 * 如果执行obj.notifyAll();所有被obj阻塞的线程都会被唤醒
			 */
			obj.notify();
		}
	}
}
运行结果:
ThreadA--执行开始
ThreadB--执行开始
ThreadB--被阻塞
ThreadA--被阻塞
ThreadC--唤醒最先被obj对象阻塞的线程
ThreadB--被唤醒
ThreadB--执行结束

总结:线程同步是为了在多线程的情况下保护数据在操作过程中的安全性,但是这样做极大的影响了软件的执行效率,所以在使用同步的过程中,应尽可能的缩小同步范围,从而减少同步对性能的影响。

时间: 2024-08-02 12:18:32

I学霸官方免费教程四十 :Java基础教程之线程同步的相关文章

I学霸官方免费教程一:Java软件开发预备知识

一.     计算机系统简介 1.硬件系统:看得见,摸得着 主机:主机箱.主板.内存.硬盘.CPU(中央处理器).声卡.显卡.网卡 外设:显示器.音响 输入设备:键盘.鼠标.扫描仪 输出设备:显示器.投影仪.音响 2.软件系统 系统软件:windows.Linux.IOS.Unix.Android: 编译软件:编程语言,编译器,解释器 应用软件:除了以上的全是应用软件,比如聊天软件,网站,游戏软件等等 二.     软件简介 软件:按照特定顺序组织的计算机数据和指令的集合:其中指令是指挥计算机如

I学霸官方免费教程三十二:Java集合框架之Set集合

Set接口 Set集合是无序的.元素不可重复的结合常用集合类有HashSet和TreeSet HashSet类常用的两种List集合各有各的优点,那么有没有同时具备这两种List集合的优点的集合呢?答案是肯定的,就是Set集合. 实例: package collection.set.hashSet; import java.util.HashSet; import java.util.Iterator; /**  * 演示HashSet  * @author 学霸联盟 - 赵灿  */ publ

I学霸官方免费教程三十一:Java集合框架之List集合

集合框架 在数组的使用过程中可以看到,想要向数组中插入元素和删除元素非常麻烦,而且数组的长度是无法改变的.java为我们提供了批量存储数据更加方便的容器,就是集合.集合和数组的作用一样,都是为了使用一个变量来存储一批数据的:但集合使用起来更加方便,而且集合的长度是可以变化的. List接口 List集合可以存储有序的,可重复的数据:常用的子类是ArrayList和LinkedList两个类 ArrayList类 这是一个底层由数组实现的集合类,是对数组进行了封装. 实例: package col

“全栈2019”Java多线程第四十二章:获取线程与读写锁的保持数

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第四十二章:获取线程与读写锁的保持数 下一章 "全栈2019"Java多线程第四十三章:查询是否有线程在等待读写锁 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复&quo

Java基础教程:JDBC编程

Java基础教程:JDBC编程 快速开始 什么是JDBC JDBC 指 Java 数据库连接,是一种标准Java应用编程接口( JAVA API),用来连接 Java 编程语言和广泛的数据库. JDBC API 库包含下面提到的每个任务,都是与数据库相关的常用用法. 制作到数据库的连接. 创建 SQL 或 MySQL 语句. 执行 SQL 或 MySQL 查询数据库. 查看和修改所产生的记录. 从根本上来说,JDBC 是一种规范,它提供了一套完整的接口,允许便携式访问到底层数据库,因此可以用 J

Java基础教程(25)--I/O流

??I/O流表示输入源或输出目标.流可以表示许多不同类型的源和目标,例如磁盘文件.设备.其他程序等. ??流支持许多不同类型的数据,包括字节.原始数据类型.字符和对象等.有些流只传递数据; 有些流则可以操纵和转换数据. ??无论各种流的内部是如何工作的,所有流都提供相同的简单模型:流是一系列数据.程序使用输入流从源头获取数据,一次一项: ??程序使用输出流将数据写入目的地,一次一项: ??在本文中,我们会看到流可以处理各种各样的数据,无论是基本数据还是复杂对象.先来几张IO流的全家福: ??In

Java基础教程 - 组合

1. 什么是组合? 如果一个类的对象和另一个类满足"has-a"关系的话,我们就可以在一个类中,把另一个类的对象作为其对象成员. 什么是"has-a"关系,举个例子:现在有一个类LapTop.class,还有一个是Moniter.class.好显然,Laptop "has-a" Moniter,也就是说,他们是满足"has-a"关系的.这时候,我们就可以把Moniter作为Laptop的一个数据成员. class Laptop

四、Android学习第四天——JAVA基础回顾(转)

(转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 四.Android学习第四天——JAVA基础回顾 这才学习Android的第四天,在程序中已经遇到了JAVA中以前不常用的一些知识点,赶紧回顾复习一下,打下基础 这里就做个简单的小结: 一.匿名内部类 匿名内部类说白了就是个没有被命名的JAVA类 在以下条件下使用匿名内部类比较适合: ①只用到该类的一个实例时 ②类在定义后被马上用到 ③类非常小(SUN推荐是在4行代码以下

Java基础教程:面向对象编程

Java基础教程:面向对象编程 Java语言概述 Java语言特点 1.Java为纯面向对象的语言,它能够直接反映现实生活中的对象.总之,Everything is object! 2.平台无关性.Java为解释型语言,编译器会把Java代码变成"""中间代码",然后在JVM上解释执行. 3.Java提供了很多内置的类库,这些类库简化了开发人员的程序设计工作,同时缩短了项目开发时间. 4.Java语言提供了对Web应用的支持. 5.Java语言提供了较好的安全性和健

Java基础教程:面向对象编程[2]

Java基础教程:面向对象编程[2] 三大特性 封装 封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装.隐藏起来的方法.封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问. 使用封装我们可以对成员变量进行更精确的控制,同时隐藏信息,实现细节等. 方法: public class Person{ private String name; private int age; ? public int getAge(){ return age;