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.独立性:进程是系统中独立存在的实体,可以拥有自己独立的资源,每个进程都拥有自己 ...
随机推荐
- robot_framework常用关键字
快捷键 F8 运行 ctrl+alt+空格 log 类似于print Set variable 定义变量 Catenate 连接对象 SEPARATOR 对多个连接信息进行分割 Create List ...
- 关于F5负载均衡你认识多少?
关于F5负载均衡你认识多少? 2018年06月09日 18:01:09 tvk872 阅读数:14008 网络负载均衡(load balance),就是将负载(工作任务)进行平衡.分摊到多个操作单 ...
- python学习-os引入
# 引入import os # 路径处理 -- 外部资源-os # 获取当前的工作路径workspace = os.getcwd() # os模块下的getcwd函数print(workspace) ...
- 在 VSCode 中 Angular 的字符串报错的问题
使用 Angular 时,报错 [tslint] " should be ' 报错原因是因为 ESLint 的严格模式,限制了使用 ' ,甚至多一个空格都会报错. 我们只需要在 settin ...
- 《Java练习题》进阶练习题(三)
编程合集: https://www.cnblogs.com/jssj/p/12002760.html 前言:不仅仅要实现,更要提升性能,精益求精,用尽量少的时间复杂度和空间复杂度解决问题. [程序68 ...
- LeetCode刷题总结-排序、并查集和图篇
本文介绍LeetCode上有关排序.并查集和图的算法题,推荐刷题总数为15道.具体考点分析如下图: 一.排序 1.数组问题 题号:164. 最大间距,难度困难 题号:324. 摆动排序 II,难度中等 ...
- Activiti架构分析及源码详解
目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...
- dotnetcore执行shell脚本
我们可以使有dotnetcore跨平台的特性,优雅的实现在dotnetcore执行shell (bash). 代码如下: using System; using System.Collections ...
- Vue增强
1. Vue事件 语法:使用v-on指令注册事件 <标签 v-on:事件句柄="表达式或者事件处理函数"></标签> 简写方式:<标签 @事件句柄=& ...
- Android高斯模糊实现方案
1.使用Glide Glide.with(this) .load(service.getImageUri()) .dontAnimate() .error(R.drawable.error_img) ...