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.独立性:进程是系统中独立存在的实体,可以拥有自己独立的资源,每个进程都拥有自己 ...
随机推荐
- 机会来了!5G时代带来新闻传播行业的变革!
5G时代到来!新闻传播行业大变革! 1.作为一名体育生进入的新闻传播学院,传统的新闻媒体能力已不再具有优势,意味着我有翻身的机会了! 从一开始进入大学,由于高中的知识储备不如其他人,尤其是英语能力方面 ...
- python基础入门 整型 bool 字符串
整型,bool值,字符串 一.整型 整型十进制和二进制 整型:整型在Python中的关键字用int来表示; 整型在计算机中是用于计算和比较的 可进行+ - * / % //(整除) **(幂运算) 十 ...
- SSM非springboot配置swagger2
前提:maven,ssm,不是springboot项目 1.在maven中添加依赖 <!-- Swagger2 Begin --> <!--springfox的核心jar包--> ...
- 2.java三大特性
1. 封装 方法:将属性值修饰为私有,提供get和set方法.造成所有对对象的访问都是通过方法的调用来完成(配合this的使用) 结果:用户不能直接随意改变一个对象内的属性,必须通过调用方法(验证)来 ...
- 中文站最好WordPress主题,自媒体,博客,企业,商城主题一网打尽
时间如流水,只能流去不流回! 点赞再看,养成习惯,这是您给我创作的动力! 本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform.W ...
- C++程序设计实验考试准备资料(2019级秋学期)
程序设计实验考试准备资料 ——傲珂 #include<bits/stdc++.h> C++常用函数: <math.h>头文件 floor() 函数原型:double floor ...
- SpringMVC 数据交互
为什么使用JSON进行数据交互? JSON数据格式比较简单.解析比较方便,在接口调用及HTML页面Ajax调用时较常用. JSON交互方式 请求是Key/Value,响应是JSON(推荐使用) 请求是 ...
- 现代前端库开发指南系列(二):使用 webpack 构建一个库
前言 在前文中,我说过本系列文章的受众是在现代前端体系下能够熟练编写业务代码的同学,因此本文在介绍 webpack 配置时,仅提及构建一个库所特有的配置,其余配置请参考 webpack 官方文档. 输 ...
- HCTF_2018-Writeup【web题】
HCTF_2018-Writeup 赛题来自:BUUCTF By:Mirror王宇阳 WarmUp: 打开赛题的页面源码(F12) <!DOCTYPE html> <html lan ...
- Java连载61-异常的机制与分类
一.is a.is like a.has a 1.is a(就是继承) public class Animal{ public void method1{ } } public class Dog e ...