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.独立性:进程是系统中独立存在的实体,可以拥有自己独立的资源,每个进程都拥有自己 ...
随机推荐
- Day 10 面向对象基础
目录 面对过程编程 面向对象编程 类 定义类 对象 定义对象 定制对象独有特征 面对过程编程 分析解决问题所需要的步骤, 用函数将这些步骤一步一步实现, 使用的时候一个个调用就可以了 优点: 复杂的问 ...
- LNMP-Nginx配置不记录静态文件、过期时间
用户访问web网站,通常日志文件会记录很多web站点上的一些静态文件信息,如果长期不处理,日志文件会越来越大,占用的系统资源也越大,此时就需要我们配置不记录静态文件和过期时间,减少日志文件记录过多不必 ...
- 伸缩容器-display:flex设置flex属性的理解
1.flex属性 1.1 flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto. flex-grow: 定义项目的放大比例,默认为0,即 ...
- Wireshark数据包分析入门
Wireshark数据包分析(一)——使用入门 Wireshark简介: Wireshark是一款最流行和强大的开源数据包抓包与分析工具,没有之一.在SecTools安全社区里颇受欢迎,曾一度超越 ...
- 备战“金九银十”10道String高频面试题解析
前言 String 是我们实际开发中使用频率非常高的类,Java 可以通过 String 类来创建和操作字符串,使用频率越高的类,我们就越容易忽视它,因为见的多所以熟悉,因为熟悉所以认为它很简单,其实 ...
- DataTable和DataReader的遍历
1.DataTable的遍历 //创建数据表 DataTable dt = GetDataTable("select * from Student"); //存储数据 String ...
- Python基础-day01-4
多文件项目的演练 开发 项目 就是开发一个 专门解决一个复杂业务功能的软件 通常每 一个项目 就具有一个 独立专属的目录,用于保存 所有和项目相关的文件 一个项目通常会包含 很多源文件 目标 在项目中 ...
- sqlserver数据库批量插入-SqlBulkCopy
当想在数据库中插入大量数据时,使用insert 不仅效率低,而且会导致一系列的数据库性能问题 当使用insert语句进行插入数据时.我使用了两种方式: 每次插入数据时,都只插入一条数据库,这个会导致每 ...
- iOS开发 - 超级签名实现之描述文件
简介 因为最近企业签掉得太严重了,上头要求实现超级签进行游戏下载.故有了此文章,记录一下过程. 签名原理其实很简单,超级签名的技术就是使用个人开发者账号,将用户的设备当作开发设备进行应用分发.这也导致 ...
- Android BGradualProgress 多种渐变、直角or弧角、进度条、加载条
可实现多种渐变.直角or弧角.进度条.加载条 (Various gradient, right or arc angle, progress bar and loading bar can be re ...