(Java多线程系列二)线程间同步
Java多线程间同步
1、什么是线程安全
通过一个案例了解线程安全
案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
先来看一个线程不安全的例子
class SellTicketRunnable implements Runnable {
public int count = 100;
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
int index = 100 - count + 1;
System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票");
count--;
}
}
}
public class JavaSyncDemo {
public static void main(String[] args) {
SellTicketRunnable runnable = new SellTicketRunnable();
Thread sellThread1 = new Thread(runnable);
Thread sellThread2 = new Thread(runnable);
sellThread1.start();
sellThread2.start();
}
}

可以看到两个线程同时卖票的时候,会出现漏卖,多卖同一张票,还会出现超卖的问题,这就是线程不安全的问题。
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
2、线程安全问题的解决办法
(1)使用同步代码块
class SellTicketRunnable implements Runnable {
public int count = 100;
private Object lock = new Object();
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
if (count > 0) {
int index = 100 - count + 1;
System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票");
count--;
}
}
}
}
}
public class JavaSyncDemo {
public static void main(String[] args) {
SellTicketRunnable runnable = new SellTicketRunnable();
Thread sellThread1 = new Thread(runnable);
Thread sellThread2 = new Thread(runnable);
sellThread1.start();
sellThread2.start();
}
}

从上面的案例可以看出,使用synchronized同步代码块包裹住写操作,每个线程在调用同步代码块中逻辑的时候,都需要先获取同步锁,所以避免了多线程写操作数据的冲突问题。
(2)使用同步函数
class SellTicketRunnable01 implements Runnable {
public int count = 100;
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.sale();
}
}
synchronized void sale() {
if (count > 0) {
int index = 100 - count + 1;
System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票");
count--;
}
}
}
public class JavaSyncDemo01 {
public static void main(String[] args) {
SellTicketRunnable01 runnable = new SellTicketRunnable01();
Thread sellThread1 = new Thread(runnable);
Thread sellThread2 = new Thread(runnable);
sellThread1.start();
sellThread2.start();
}
}
synchronized包裹的函数,其实就是给该函数块添加了一把this锁。
注意:synchronized 修饰静态方法使用锁是当前类的字节码文件(即
类名.class),同理,如果在静态方法中添加个同步代码块,可以获取类名.class为代码块加锁
class SellTicketRunnable02 implements Runnable {
public static int count = 100;
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
SellTicketRunnable02.sale();
}
}
static void sale() {
synchronized (SellTicketRunnable02.class) {
if (count > 0) {
int index = 100 - count + 1;
System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票");
count--;
}
}
}
}
public class JavaSyncDemo02 {
public static void main(String[] args) {
SellTicketRunnable02 runnable = new SellTicketRunnable02();
Thread sellThread1 = new Thread(runnable);
Thread sellThread2 = new Thread(runnable);
sellThread1.start();
sellThread2.start();
}
}
(3)使用lock锁
class SellTicketRunnable03 implements Runnable {
public int count = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
if (count > 0) {
int index = 100 - count + 1;
System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票");
count--;
}
lock.unlock();
}
}
}
public class JavaSyncDemo03 {
public static void main(String[] args) {
SellTicketRunnable03 runnable = new SellTicketRunnable03();
Thread sellThread1 = new Thread(runnable);
Thread sellThread2 = new Thread(runnable);
sellThread1.start();
sellThread2.start();
}
}
lock和synchronized的区别
①lock在使用时需要手动的获取锁和释放锁;
②lock可以尝试非阻塞的获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁;
③lock锁可以响应中断,当获取到锁的线程被中断时,中断异常会被抛出,同时锁被释放;
④lock在指定截至时间之前获取锁,如果解释时间到了依旧无法获取锁,就返回。
// lock锁的安全使用方法
class lockDemo {
Lock lock = new ReentrantLock();
void demoFun() {
lock.lock();
try {
// 可能出现线程安全的操作
} finally {
lock.unlock();
}
}
}
(4)使用Java原子类
java.util.concurrent.atomic.AtomicBoolean;
java.util.concurrent.atomic.AtomicInteger;
java.util.concurrent.atomic.AtomicLong;
java.util.concurrent.atomic.AtomicReference;
class SellTicketRunnable04 implements Runnable {
public AtomicInteger count = new AtomicInteger(100);
@Override
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count.get() > 0) {
int index = 100 - count.getAndDecrement() + 1;
System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票");
}
}
}
}
public class JavaSyncDemo04 {
public static void main(String[] args) {
SellTicketRunnable04 runnable = new SellTicketRunnable04();
Thread sellThread1 = new Thread(runnable);
Thread sellThread2 = new Thread(runnable);
sellThread1.start();
sellThread2.start();
}
}
3、死锁
先看一个死锁的示例
public class DeadLockDemo01 {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread() { //线程1
public void run() {
while (true) {
synchronized (lock1) {
System.out.println(this.getName() + ":获取lock1锁");
synchronized (lock2) {
System.out.println(this.getName() + ":获取lock2锁");
}
}
}
}
}.start();
new Thread() { //线程2
public void run() {
while (true) {
synchronized (lock2) {
System.out.println(this.getName() + ":获取lock2锁");
synchronized (lock1) {
System.out.println(this.getName() + "::获取lock1锁");
}
}
}
}
}.start();
}
}

运行上面的代码,可以观察到线程卡死,就是出现了死锁
线程1先拿到lock1锁,再拿到lock2锁,执行完成后才能释放所有锁;
线程2先拿到lock2锁,再拿到lock1锁,执行完成后才能释放所有锁。
如果在线程1获取到lock1锁的时候,线程2获取到lock2还没释放,线程1无法获取lock2锁,也就无法释放lock2锁,这时系统就会出现死锁。
线程死锁的避免办法:不要在同步中嵌套同步
(Java多线程系列二)线程间同步的更多相关文章
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”06之 Callable和Future
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- java多线程系列(二)
对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
- java多线程系列(二)---对象变量并发访问
对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
- java多线程系列(六)---线程池原理及其使用
线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...
- Java多线程系列--“JUC线程池”01之 线程池架构
概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构 ...
随机推荐
- Dom编程-左侧菜单栏设计模型实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Codeforces 1237E. Balanced Binary Search Trees
传送门 这一题是真的坑人,时间空间都在鼓励你用 $NTT$ 优化 $dp$...(但是我并不会 $NTT$) 看到题目然后考虑树形 $dp$ ,设 $f[i][0/1]$ 表示 $i$ 个节点的树,根 ...
- 向量运算(lua,三维) 点乘、叉乘、模、夹角
向量运算在游戏制作中经常用到,稍微总结一下. 一.点乘 如图,假设 向量a与b的点乘表示a在b上的投影与b的模的乘积 公式: 代码: function MathHelper.GetVector3D ...
- JDBC24homework
编写程序: 创建一个类DBTools,在DBTools中创建一个方法find,find方法用于对数据库进行查询操作,现在要求将结果集封装成数组线性表嵌套数组的形式: ArrayList<Stri ...
- Flink概述
计算引擎 大数据计算引擎分为离线计算和实时计算,离线计算就是我们通常说的批计算,代表是Hadoop MapReduce.Hive等大数据技术.实时计算也被称作流计算,代表是Storm.Spark St ...
- 搭建自己的框架WedeNet(五)
WedeNet2018.WedeWcfServices-WCF服务层:结构如下: 就是定义了服务契约接口和服务类,以OrderServices为例,如下: using System; using Sy ...
- JavaScript例子1-给网页中所有<p>元素添加onclick事件
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- Java学习 面向对象(下)——Java疯狂讲义th4
面向对象(下) [TOC] 包装类 通过包装类可以把8个基本类型的值包装成对象使用. 自动拆箱.自动装箱 把字符串类型值转换成基本类型的值: 包装类的 parseXxx(String s)静态方法 包 ...
- 【持续集成工具】 Jenkins
一.什么是持续集成 持续集成(CI):简单来说就是指将开发者的工作内容频繁地集成到主干中. 而持续集成工具可以将开发者频繁需要构建,编译,测试,部署等操作自动进行,为开发提供了非常大便利. 二.持续集 ...
- 内网渗透之frp使用
0x00 前言 nps相比上次已经介绍过了.但是他有一个致命缺点就是在scks5代理下会长连接一直不放开导致结果不准确.所以来讲讲frp的使用.frp虽然需要落地配置文件,但是扫描的结果还是很准确的. ...