java基础之多线程三:多线程并发同步
由于线程的执行是CPU随机调度的,比如我们开启10个线程,这10个线程并不是同时执行的,而是CPU快速的在这10个线程之间切换执行,由于切换速度极快使我们感觉同时执行罢了。
线程同步问题往往发生在多个线程调用同一方法或者操作同一变量,但是我们要知道其本质就是CPU对线程的随机调度,CPU无法保证一个线程执行完其逻辑才去调用另一个线程执行。
比如:
4个窗口同时售卖100张车票:
public static void main(String[] args) { //测试: 卖票的动作.
//1. 因为是四个窗口, 所以需要创建四个线程对象. 给线程自定义名字
MyThread mt1 = new MyThread("窗口1");
MyThread mt2 = new MyThread("窗口2");
MyThread mt3 = new MyThread("窗口3");
MyThread mt4 = new MyThread("窗口4");
//2. 开启线程
mt1.start();
mt2.start();
mt3.start();
mt4.start();
}
public class MyThread extends Thread{
//需求: 四个窗口, 卖100张票.
/*
* 思路:
* 1. 定义一个变量(tickets), 记录票数.
* 2. 因为是四个窗口 同时 卖票, 通过多线程卖票.
*/
//1. 定义一个变量(tickets), 记录票数.
private static int tickets = 100; //因为是共享数据, 所以用static修饰
//3. 使用父类的构造方法
public MyThread() {
super();
}
public MyThread(String name) {
super(name);
}
//2. 因为是四个窗口 同时 卖票, 通过多线程卖票.
@Override
public void run() {
/*
* 卖票的动作 的 思路:
* A: 因为不知道还有多少张票要买, 所以用while(true).
* B: 做一下越界处理. 没票就不卖了.
* C: 如果有票, 就正常的卖票即可.
*/
//A
while(true) {
//B
if (tickets < 1) {
break;
}
//为了加大出现错误票的概率, 我们加入: 休眠线程的概念
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//C // 线程2休眠, 线程3休眠, 线程4休眠,
System.out.println(getName() + "正在出售第" + tickets-- + "张票");
/*
* 出现负数票的原因: if()判断, 休眠线程
* 假设现在卖到最后一张票了, tickets的值应该是1, 此时,
* 如果线程1抢到了资源, 会越过if(), 然后休眠, 以此类推, 四个线程都处于休眠的状态.
*
* 休眠时间过了之后, 程序继续运行.
* 假设线程1先抢到资源, 打印: 出售1号票, 然后会把tickets的值改为: 0
* 假设线程2后抢到资源, 打印: 出售0号票, 然后会把tickets的值改为: -1
* 假设线程3后抢到资源, 打印: 出售-1号票, 然后会把tickets的值改为: -2
* 假设线程4后抢到资源, 打印: 出售-2号票, 然后会把tickets的值改为: -3
*/
/*
* 出现重复值的原因: tickets--
* tickets-- 相当于 tickets = tickets - 1
* tickets-- 做了 3件事:
* A: 读值. 读取tickets的值.
* B: 改值. 将tickets的值 - 1.
* C: 赋值. 将修改后的值重新赋值给 tickets.
* 还没有来得及执行 C的动作, 此时别的线程抢走资源了, 就会出现重复值.
*
*/
}
}
}
运行结果会出现下面这种一张票重复售卖或者出现卖负数票的情况:
窗口2正在出售第99张票
窗口1正在出售第98张票
窗口3正在出售第99张票
窗口3正在出售第95张票
窗口3正在出售第94张票
窗口3正在出售第93张票
所以我们可以说这种多个线程同时并发操作同一数据的时候会出现错误,是有安全问题的。
解决方案:
可使用同步代码块或同步方法来解决(synchronized )
//A
while(true) {
//while循环中的代码就是一次完成的卖票过程, 之所以会出现非法值的票,
//原因是因为: 某个线程在卖票期间, 被别的线程抢走CPU资源了.
//其实解决方案很简单: 在某一个线程卖(一次)票期间, 别的线程不能干预. synchronized (MyThread.class) {
//要加锁的代码
//B
if (tickets < 1) {
break;
} //为了加大出现错误票的概率, 我们加入: 休眠线程的概念
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
} //C // 线程2休眠, 线程3休眠, 线程4休眠,
System.out.println(getName() + "正在出售第" + tickets-- + "张票");
} }
综上所述,我们发现如果多个线程同时操作同一数据的情况,使用同步代码块或方法同步就可以解决了。
那么我们下面说一下同步代码块和同步方法的锁对象:
非静态方法同步对应的锁是this
public class Demo {
//同步代码块
public void method1() {
synchronized (this) {
System.out.print("山");
System.out.print("东");
System.out.print("张");
System.out.print("学");
System.out.print("友");
System.out.print("\r\n");
}
}
//同步方法
public synchronized void method2() {
System.out.print("s");
System.out.print("d");
System.out.print("z");
System.out.print("x");
System.out.print("y");
System.out.print("\r\n");
}
}
静态方法同步对应的锁是当前类的字节码文件对象:
public class Demo {
//静态方法代码块同步
public static void method1() {
synchronized (Demo.class) {
System.out.print("山");
System.out.print("东");
System.out.print("张");
System.out.print("学");
System.out.print("友");
System.out.print("\r\n");
}
}
//同步静态方法
public static synchronized void method2() {
System.out.print("s");
System.out.print("d");
System.out.print("z");
System.out.print("x");
System.out.print("y");
System.out.print("\r\n");
}
}
这里说明一下:
/*
* 静态同步方法 和 非静态同步方法的锁对象
* 静态方法:
锁对象: 该类的字节码文件对象. 也就是上面的Demo.class
非静态方法:
锁对象: this
*/
总结:普通同步方法的锁对象是this,静态同步方法的锁对象是类的字节码文件对象
当前类实例对象,同步代码块锁可以自己定义,只要保证操作同一数据的线程使用的是同一把锁即可。
java基础之多线程三:多线程并发同步的更多相关文章
- java基础解析系列(三)---HashMap
java基础解析系列(三)---HashMap java基础解析系列 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- Java基础系列3:多线程超详细总结
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 1.线程概述 几乎所 ...
- 【Java基础】【25多线程(下)&GUI】
25.01_多线程(单例设计模式)(掌握) 单例设计模式:保证类在内存中只有一个对象. 如何保证类在内存中只有一个对象呢? (1)控制类的创建,不让其他类来创建本类的对象.private (2)在本类 ...
- Java提高班(三)并发中的线程同步与锁
乐观锁.悲观锁.公平锁.自旋锁.偏向锁.轻量级锁.重量级锁.锁膨胀...难理解?不存的!来,话不多说,带你飙车. 上一篇介绍了线程池的使用,在享受线程池带给我们的性能优势之外,似乎也带来了另一个问题: ...
- Java基础(七)——多线程
一.概述 1.介绍 Java VM 启动的时候会有一个进程Java.exe,该进程中至少有一个线程负责Java程序的执行.而且这个线程运行的代码存在于main方法中,该线程称之为主线程.其实从细节上来 ...
- 【Java基础】【24多线程(上)】
24.01_多线程(多线程的引入)(了解) 1.什么是线程 线程是程序执行的一条路径, 一个进程中可以包含多条线程 多线程并发执行可以提高程序的效率, 可以同时完成多项工作 2.多线程的应用场景 红蜘 ...
- JAVA基础学习-集合三-Map、HashMap,TreeMap与常用API
森林森 一份耕耘,一份收获 博客园 首页 新随笔 联系 管理 订阅 随笔- 397 文章- 0 评论- 78 JAVA基础学习day16--集合三-Map.HashMap,TreeMap与常用A ...
- Java基础知识点(三)
前言:准备将Java基础知识点总结成一个系列,用于平常复习并加深理解.每篇尽量做到短小精悍,便于阅读. 1.Math类中相关函数 Math.floor(x):返回不大于x的最大整数.eg:Math.f ...
- Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)
多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...
- Java 并发和多线程(三) 多线程的代价 [转]
原文链接:http://tutorials.jenkov.com/java-concurrency/costs.html 作者:Jakob Jenkov 翻译:古圣昌 校对:欧振 ...
随机推荐
- HTTP返回结果状态码小结
HTTP 状态码负责表示客户端 HTTP 请求的返回结果.标记服务器端的处理是否正常.通知出现的错误等工作. 一.状态码的类别 状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果.借助状态 ...
- ajax跨域解决
https://blog.csdn.net/csdn_ds/article/details/73691134 Access-Control-Allow-Origin 跨域设置多域名:http://ww ...
- Robot Framework接口测试(1)
RF是做接口测试的一个非常方便的工具,我们只需要写好发送报文的脚本,就可以灵活的对接口进行测试. 做接口测试我们需要做如下工作: 1.拼接发送的报文 2.发送请求的方法 3.对结果进行判断 我们先按步 ...
- test20181219 连续段的期望
题意 连续段的期望 [问题描述] 小N最近学习了位运算,她发现2个数xor之后数的大小可能变大也可能变小,and之后都不会变大,or之后不会变小.于是她想算出以下的期望值:现在有 N个数排成一排,如果 ...
- HttpContext.Current.Cache和HttpRuntime.Cache的区别,以及System.Runtime.Caching
先看MSDN上的解释: HttpContext.Current.Cache:为当前 HTTP 请求获取Cache对象. HttpRuntime.Cache:获取当前应用程序的Cac ...
- Linux 中断下半部
为什么使用中断下半部? 中断执行的原则是要以最快的速度执行完,而且期间不能延时和休眠! 可是现实中,中断中可能没办法很快的处理完需要做的事,或者必须用到延时和休眠,因此引入了中断下半部. 中断中处理紧 ...
- css学习笔记之图像
图像与文本的对齐方式: vertical-align:text-top;表示的意思是图像的顶部和同一行的文本对齐,但文本不会超出图片的上边线. vertical-align:middle;表示的意思是 ...
- 在 Docker 中运行 MySQL
首先启用 Windows 10 的容器功能,然后去 Docker 的官网,下载安装包. 跟着安装程序走完流程,Docker 就在 Windows 上愉快的游起来啦~ 设置镜像地址 Docker 在创建 ...
- 几种经典的hash算法
计算理论中,没有Hash函数的说法,只有单向函数的说法.所谓的单向函数,是一个复杂的定义,大家可以去看计算理论或者密码学方面的数据.用“人 类”的语言描述单向函数就是:如果某个函数在给定输入的时候,很 ...
- Java-Runoob:Java 对象和类
ylbtech-Java-Runoob:Java 对象和类 1.返回顶部 1. Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 ...