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.独立性:进程是系统中独立存在的实体,可以拥有自己独立的资源,每个进程都拥有自己 ...
随机推荐
- 【CuteJavaScript】GraphQL真香入门教程
看完复联四,我整理了这份 GraphQL 入门教程,哈哈真香... 欢迎关注我的 个人主页 && 个人博客 && 个人知识库 && 微信公众号" ...
- 使用CleanWebpackPlugin插件报错原因:CleanWebpackPlugin is not a constructor
// webpack版本:4.32.2 // 抛错原写法 const CleanWebpackPlugin = require("clean-webpack-plugin"); . ...
- 【NPM】使用学习
[NPM]使用学习 转载: 目录 ============================================== 1.修改 npm 模块的安装路径 2.淘宝 NPM 镜像 3.vue-c ...
- 【hibernate】存储图片
[hibernate]存储图片 转载: package cn.ycx.study.hibernate.entity; import javax.persistence.Entity; import j ...
- 用故事说透 HTTPS
本文来自素燕公众号,原文地址:用故事说透 HTTPS 故事中的主演:小华今年上大一,这是她第一次离开父母,独自一人到北京上学.今天妈妈的生日,想了想要给妈妈一个祝福,便给妈妈发了条消息:妈妈收到这条消 ...
- 【30天自制操作系统】day04:C语言与目前执行流程图
用 C 语言直接写入内存 原来依靠汇编 void io_hlt(void); void write_mem8(int addr, int data); void HariMain(void){ int ...
- Java编译时常量和运行时常量
Java编译时常量和运行时常量 编译期常量指的就是程序在编译时就能确定这个常量的具体值. 非编译期常量就是程序在运行时才能确定常量的值,因此也称为运行时常量. 在Java中,编译期常量指的是用fina ...
- AssemblyBuilder以及Activator双剑合璧
AssemblyBuilder和Activator两个类是DispatchProxy类实现动态代理以及AOP的根本,示例demo可参考 DispatchProxy实现动态代理及AOP .Assembl ...
- spring的事物管理配置
基于注解的事务管理器配置(AOP) 首先要引入AOP和TX的名称控件 <!-- 使用annotation定义事务 --> <tx:annotation-driven transact ...
- LINE 项目总结
前段时间做了一个“国外的公众号”,为什么说是国外的公众号呢,因为他和微信的公众号有很多共通之处.当然了也有很多不同的地方,第一个要说的就是,我在打印信息的时候,使用的alert()弹窗,但是后台才发现 ...