Java 之 线程安全(线程同步)
一、线程安全
当有多个线程同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,这就是线程安全的。
下面通过一个案例来演示线程的安全问题。
模拟电影票买票的过程,其中,一共有100张票。下面来模拟电影票的售票窗口,实现多个窗口同时卖票,采用线程对象来模拟,通过实现 Runnable 接口子类来模拟。
Demo:
// 模拟票
public class Ticket implements Runnable {
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while (true) {
if (ticket > 0) {//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket+"张票");
ticket--;
}
}
}
}
// 测试类
public class Demo {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个窗口对象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同时卖票
t1.start();
t2.start();
t3.start();
}
}
结果中有一部分这样现象:

发现程序出现了两个问题
① 相同的票数,比如5这张票被卖了两次。
② 不存在的票,比如 0 和 -1票,是不存在的。
为什么出现这样的情况呢?
当只有一个窗口售票或多个窗口分别出售自己的票是没有问题的。但是当三个窗口,同时访问共享的资源,就会导致线程不同步,这种问题称为线程不安全。

线程安全问题产生的原理:

注意:线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量,静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
二、线程同步
当使用过个线程访问统一资源的时候,且多个线程中对资源有些的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票的问题。Java 中提供了同步机制(synchronized)来解决。
以上面的售票案例来简述一下同步机制:
当窗口1线程进入操作的时候,窗口2和窗口3线程只能在外面等着,窗口1操作结束,窗口1、窗口2和窗口3才有机会进入代码去执行。
也就是说某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
同步原理图解:

为了保证每个线程都能正常执行原子操作,Java 引入线程同步机制,下面学习三种同步机制。
1、同步代码块方法
同步代码块:synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
语法格式:
synchronized(同步锁){
需要同步操作的代码 / 可能会出现线程安全问题的代码(访问共享数据的代码)
}
同步锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。
锁对象:又称为监视器对象,同一时刻,某一段代码,只允许一个线程允许,其他线程进不来。
注意:
① 代码块中的锁对象,可以使用任意的对象
② 必须保证多个线程使用的锁对象是同一个
③ 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
④ 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着 (Blocked)
Demo : 使用同步代码块解决上面售票问题
(1)使用 Thread 类实现
class Ticket extends Thread{
private static int total = 100;
private static Object lock = new Object();//锁的选择之一,单独造一个锁对象
public Ticket(String name) {
super(name);
}
public void run(){
// synchronized (this) {//这里使用this不行,因为这个this,对于三个线程来说不是同一个
while(true){
synchronized (lock) {
if(total > 0){
System.out.println(getName() + "卖出一张票");
total--;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}else{
break;
}
}
}
}
}
(2)使用 Runnable接口 实现:方式一
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//同步代码块
synchronized (obj){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
方式二:使用 this 锁
class Ticket implements Runnable{
private int total = 10;
@Override
public void run() {
while(true){
synchronized (this) {//选择this当锁,可以,因为只有一个Ticket的对象
if(total>0){
System.out.println(Thread.currentThread().getName() +"卖出一张票");
total--;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}else{
break;
}
}
}
}
}
2、同步方法
同步方法:使用 synchronized 修饰的方法,就叫做 同步方法,保证一个线程执行该方法的时候,其他线程只能在方法外等着。
语法格式:
【修饰符】 synchronized 返回值类型 方法名(【参数列表】){
可能会产生线程安全问题的代码 / 可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:同步方法的锁对象,程序员无法选择:
(1)对于非静态的方法,同步锁就是实现类对象,也就是 this。
(2)对于静态方法,同步锁对象就是当前类的 Class 对象。
Demo:使用同步方法解决售票问题。
(1)使用 Thread 类实现
class Ticket extends Thread{
private static int total = 100;
public Ticket(String name) {
super(name);
}
public void run(){
while(total>0){//程序停止的条件
saleOneTicket();
}
}
//非静态方法的锁对象是this,这里使用this,不是合格的锁对象
//使用非静态方法,当前锁对象是 当前的 Class 对象
public synchronized static void saleOneTicket(){
if(total > 0){//线程安全问题的条件
System.out.println(Thread.currentThread().getName() + "卖出一张票");
total--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}
}
}
(2)使用 Runnable 接口实现
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
System.out.println("this:"+this); //使用死循环,让卖票操作重复执行
while(true){
payTicketStatic();
}
}
/*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁?
就是实现类对象 new RunnableImpl()
也是就是this
*/
public /*synchronized*/ void payTicket(){
synchronized (this){ //这里可以使用 this 锁对象
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
还可以把同步方法声明为一个静态的方法:
语法格式:
public static synchronized void method(){
可能会产生线程安全问题的代码 / 可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:对于 static方法,锁对象就是使用当前方法所在类的字节码对象(类名.class)。
Demo:使用 static 同步方法解决售票问题。
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private static int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
payTicketStatic();
}
}
/*
静态的同步方法
静态方法的锁对象是本类的class属性-->class文件对象(反射)
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
3、Lock 锁
java.util.concurrent.locks.Lock 机制提供了比 synchronized 代码块和 synchronized 方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock 锁也称同步锁,方法如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
使用步骤:
① 在成员位置创建一个ReentrantLock对象(Lock接口的一个实现类)
② 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
③ 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
Demo:
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//1.在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock();
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock();//无论程序是否异常,都会把锁释放
}
}
}
}
}
三、死锁
1、死锁概念
死锁:死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
2、死锁的必要条件
死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件
Java 之 线程安全(线程同步)的更多相关文章
- 第22章 java线程(2)-线程同步
java线程(2)-线程同步 本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式 1.线程不安全问题 什么叫线程不安全呢 即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题 对于前 ...
- Java线程:线程的同步-同步方法
Java线程:线程的同步-同步方法 线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...
- (转)Java线程:线程的同步与锁
Java线程:线程的同步与锁 一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Fo ...
- -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait( ...
- Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)
关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨Lock对象. synchronize ...
- Java:多线程,线程同步,synchronized关键字的用法(同步代码块、非静态同步方法、静态同步方法)
关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨synchronized关键字. sy ...
- Java并发编程,互斥同步和线程之间的协作
互斥同步和线程之间的协作 互斥同步 Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLo ...
- Java线程和多线程(三)——线程安全和同步
线程安全在Java中是一个很重要的课题.Java提供的多线程环境支持使用Java线程.我们都知道多线程共享一些对象实例的话,可能会在读取和更新共享数据的事后产生数据不一致问题. 线程安全 之所以会产生 ...
- Java多线程——线程之间的同步
Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,如何使用volatile关键字,如何使用synchronized修饰的同步代码块和同步方法解决线程安全问题. 部分内容来自以 ...
- Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)
多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...
随机推荐
- woocommerce分类页产品数量修改
我们用woocommerce建商城,不同的模板分类页产品数量不同,如果想要显示更多或更少的数量要如何修改呢?很简单,一行代码就能搞定!打开当前主题的function.php文件,加入如下代码,把18改 ...
- jQuery的Promise 这里介绍的很详细
原文电梯:https://www.cnblogs.com/yelongsan/p/7644239.html 先前了解了ES6的Promise对象,来看看jQuery中的Promise,也就是jQuer ...
- views视图
1.request.POST.get('.......') --radio 单选框 get()方法 从HTML中提取发过来的数据 1. 2. 3. 4. 2.request.POS ...
- 冬令营DAY3 T1 Matrix
题目描述 Description 生活中,我们常常用 233 表示情感.实际上,我们也会说 2333,23333,等等. 于是问题来了: 定义一种矩阵,称为 233 矩阵.矩阵的第一行依次是 2 ...
- 在执行一行代码之前CLR做的68件事
因为CLR是一个托管环境,所以运行时中有几个组件需要在执行任何代码之前初始化.本文将介绍EE(执行引擎)启动程序,并详细检查初始化过程.68只是一个粗略的指南,它取决于您使用的运行时版本.启用了哪些功 ...
- 无旋treap大法好
无旋Treap大法好 原理? 是一棵二叉查找树: 一个节点左子树权值都比他小,右子树权值都比他大 所以可以维护序列(以位置为权值),或数值(以数值为权值) 是一个堆: 每个节点除了上述提到的权值外,还 ...
- 剑指offer15 二进制中1的个数
题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数.例如,把9表示成二进制是1001,有2位是1.因此,如果输入9则函数输出2. int Number(int n) { ; while ...
- Linux stty命令
stty是linux下改变和打印终端设置的常用命令. 一.参数: 1.打印终端行设置 -a,--all 以人可读的方式打印所有当前设置:-a参数比单独的stty命令输出的终端信息更详细 -g,-- ...
- Linux系统查看是32位还是64位
uname -a 如果是64位机器,会输出x86_64
- SharePoint - Another Way to Delete Site Collection
I had created a site collection. But there is a problem of web-frontend server (I did not know when ...