Java之线程安全
什么是线程安全?
什么是线程安全问题?
我们通过一个案例,演示线程的安全问题:电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。
package demo03ThreadSafe;
public class RunnableImpl implements Runnable {
//定义一个多个线程共享的票源
private int ticket = 10;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while (true) {
//先判断票是否存在
if (ticket > 0) {
try {
//先判断票是否存在
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println("线程名字是:" + Thread.currentThread().getName() + "正在卖" + ticket + "票");
ticket--;
}
}
}
}
package demo03ThreadSafe;
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl runnable = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread thread1 = new Thread(runnable, "窗口1");
Thread thread2 = new Thread(runnable, "窗口2");
Thread thread3 = new Thread(runnable, "窗口3");
//调用start方法开启多线程
thread1.start();
thread2.start();
thread3.start();
}
}
代码执行后结果

- 相同的票数,比如5这张票被卖了两回。
- 不存在的票,比如0票与-1票,是不存在的。
线程安全问题的产生的原因
如何解决线程安全问题
/* 窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码 去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU 资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。 */
- 同步代码块。
- 同步方法。
- 锁机制。
同步代码块
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
- 通过代码块中的锁对象,可以使用任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行,在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
package demo04Synchronized;
public class Demo01Synchronized implements Runnable {
//定义一个多个线程共享的票源
private int ticket = 10;
//定义同步锁
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while (true) {
//同步代码块
synchronized (obj) {
//先判断票是否存在
if (ticket > 0) {
try {
//先判断票是否存在
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println("线程名字是:" + Thread.currentThread().getName() + "正在卖" + ticket + "票");
ticket--;
}
}
}
}
}
测试类
package demo04Synchronized;
public class SynchronizedTest {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
Demo01Synchronized runnable = new Demo01Synchronized();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread thread1 = new Thread(runnable, "窗口1");
Thread thread2 = new Thread(runnable, "窗口2");
Thread thread3 = new Thread(runnable, "窗口3");
//调用start方法开启多线程
thread1.start();
thread2.start();
thread3.start();
}
}
代码执行后的结果

同步技术的原理
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器。多个线程一起抢夺CPU的执行权,谁抢到了锁对象,谁才能进入同步代码块中操作共享数据。没有抢夺到锁对象的线程即使拥有CPU的执行权也会进入线程阻塞状态。总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁无法进入同步代码块,同步保证了只能有一个线程在同步中执行共享数据,保证了安全。但是程序会频繁的判断锁,获取锁,其执行效率会降低。
同步方法
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
- 对于非static方法,同步锁就是this。
- 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
使用同步方法模拟票
package demo05Method;
/*
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的二种方案:使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
*/
public class RunnableImpl implements Runnable {
//定义一个多个线程共享的票源
private static int ticket = 10;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while (true) {
//调用同步方法
payTicketStatic();
}
}
//定义同步方法
public static synchronized void payTicketStatic() {
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
定义测试类
package demo05Method;
public class MethodTest {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl runnable = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread thread1 = new Thread(runnable, "窗口1");
Thread thread2 = new Thread(runnable, "窗口2");
Thread thread3 = new Thread(runnable, "窗口3");
//调用start方法开启多线程
thread1.start();
thread2.start();
thread3.start();
}
}
代码执行后的结果

解决线程安全问题的三种方案:使用Lock锁
- public void lock() :加同步锁。
- public void unlock() :释放同步锁
使用步骤:
- 在成员位置创建一个ReentrantLock对象(java.util.concurrent.locks.ReentrantLock implements Lock接口)
- 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
- 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
使用Lock锁模拟卖票
package demo06Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest implements Runnable {
//定义一个多个线程共享的票源
private int ticket = 10;
//1:在成员位置创建一个ReentrantLock对象
ReentrantLock rl = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while (true) {
//2:在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
rl.lock();
//先判断票是否存在
if (ticket > 0) {
try {
//先判断票是否存在
Thread.sleep(100);
//票存在,卖票 ticket--
System.out.println("线程名字是:" + Thread.currentThread().getName() + "正在卖" + ticket + "票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
rl.unlock();//无论程序是否异常,都会把锁释放
}
}
}
}
}
定义测试类
package demo06Lock;
public class DemoLockTest {
public static void main(String[] args) {
//创建接口实现类对象
LockTest l = new LockTest();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread thread1 = new Thread(l, "窗口1");
Thread thread2 = new Thread(l, "窗口2");
Thread thread3 = new Thread(l, "窗口3");
//调用start方法开启多线程
thread1.start();
thread2.start();
thread3.start();
}
}
代码执行后的结果

Java之线程安全的更多相关文章
- java之线程
java之线程 一:线程: 线程是什么呢?线程,有时被称为轻量级进程是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成.另外,线程是进程中的一个实体,是被系统 ...
- Java 使用线程方式Thread和Runnable,以及Thread与Runnable的区别
一. java中实现线程的方式有Thread和Runnable Thread: public class Thread1 extends Thread{ @Override public void r ...
- Java的线程安全
线程安全 我们这里讨论的线程安全,就限定于多个线程之间存在共享数据访问这个前提,因为如果一段代码根本不会与其他线程共享数据,那么从线程安全的角度来看,程序是串行执行还是多线程执行对它来说是完全没有区别 ...
- 深入理解Java之线程池
原作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...
- java中线程分两种,守护线程和用户线程。
java中线程分为两种类型:用户线程和守护线程. 通过Thread.setDaemon(false)设置为用户线程: 通过Thread.setDaemon(true)设置为守护线程. 如果不设置次属性 ...
- java 多线程—— 线程让步
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- java 多线程—— 线程等待与唤醒
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java的线程模型
并发不一定要依赖多线程(如PHP中很常见的多进程并发),但是在Java里面谈论并发,大多数都与线程脱不开关系. 线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开, ...
- Java多线程 - 线程状态
转自: http://www.cnblogs.com/lwbqqyumidi/p/3804883.html 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的 ...
- Java Thread线程控制
一.线程和进程 进程是处于运行中的程序,具有一定的独立能力,进程是系统进行资源分配和调度的一个独立单位. 进程特征: A.独立性:进程是系统中独立存在的实体,可以拥有自己独立的资源,每个进程都拥有自己 ...
随机推荐
- Python3 函数基础2
目录 可变长参数 可变长形参: *args 可变长实参: *容器类 可变长形参: **kwargs 可变长实参: **字典 函数对象 引用 当做容器类型元素 当做参数传给一个函数 当做函数的返回值 函 ...
- Day 03 作业
简述变量的组成 变量名,赋值符号,变量值 简述变量名的命名规范 变量名应该能反映变量值所描述的状态 变量名必须以字母数字下划线组合且不能以数字开头 变量名不能是关键字 简述注释的作用 让后面的代码失效 ...
- 轻松构建基于 Serverless 架构的弹性高可用音视频处理系统
前言 随着计算机技术和 Internet 的日新月异,视频点播技术因其良好的人机交互性和流媒体传输技术倍受教育.娱乐等行业青睐,而在当前, 云计算平台厂商的产品线不断成熟完善, 如果想要搭建视频点播类 ...
- 6张图说清楚Tomcat原理及请求流程
前言 很多东西在时序图中体现的已经非常清楚了,没有必要再一步一步的作介绍,本文以图为主,然后对部分内容加以简单解释. 绘制图形使用的工具是 PlantUML + Visual Studio Code ...
- 记录我的 python 学习历程-Day07 基础数据类型进阶 / 数据类型之间的转换 / 基础数据类型总结 / 编码的进阶
基础数据类型 str(字符串) str:补充方法练习一遍就行 s.capitalize() 首字母大写,其余变小写 s = 'dyLAn' print(s.capitalize()) # Dylan ...
- exports、module.exports 和 export、export default
先了解他们的使用范围. require: node 和 es6 都支持的引入export / import : 只有es6 支持的导出引入module.exports / exports: 只有 no ...
- Kubernetes服务发现入门:如何高效管理服务?
愈发复杂的应用程序正在依靠微服务来保持可扩展性和提升效率.Kubernetes为微服务提供了完美的环境,并能够让其与Kubernetes的工具组件和功能兼容.当应用程序的每个部分放置在一个容器中,整个 ...
- Python基础-day01-4
多文件项目的演练 开发 项目 就是开发一个 专门解决一个复杂业务功能的软件 通常每 一个项目 就具有一个 独立专属的目录,用于保存 所有和项目相关的文件 一个项目通常会包含 很多源文件 目标 在项目中 ...
- Django-Model 大全
ORM 映射关系: 表名 <-------> 类名 字段 <-------> 属性 表记录 <-------> 类实例对象 创建表(建立模型) 实例:我们来假定 ...
- [ASP.NET Core 3框架揭秘] 文件系统[4]:程序集内嵌文件系统
一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以采用统一的编程方式来读取内嵌的资源文件,该类型定义在 "Microsoft.Ex ...