(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的最后一部分的内容——线程池.内容包括:线程池架构 ...
随机推荐
- docker CMD 和 ENTRYPOINT 区别
昨天用Dockerfile来启动mongodb的集群,启动参数--replSet死活没执行,最后就决定研究一哈cmd和entrypoint.但是上网看了一些资料个人觉得讲的不好,还是没有说出根本的东西 ...
- linux常用的bash指令
文本处理 awk sed grep sort uniq cat cut echo fmt tr nl egrep fgrep wc 进程监视 ps top htop atop lsof 网络 nmap ...
- 记项目管理大作业Web项目Mandrian的全流程[其一] 整体分析: 功能划分, 组织结构
Mandrian是个图书管理系统, 具体需求老师给出 这个项目的目的主要是管理过程和高层设计的学习和实践 11人小组, 路人局 成员调查 这里由于很多人我都不认识, 所以我提前发了一个能力调查表, 调 ...
- javascript&jquery方法比对
参考链接:https://juejin.im/post/5d2705d8e51d4577407b1dda 参考评论链接http://youmightnotneedjquery.com/ javascr ...
- 【Java】 BIO与NIO以及AIO分析
一.BIO与NIO以及AIO的概念 BIO是同步阻塞式的IO NIO是同步非阻塞的IO (NIO1.0,JDK1.4) AIO是非同步非阻塞的IO(NIO2.0,JDK1.7) 二.BIO简单分析 1 ...
- umi+antdpro 2.3
关于umi接管了路由之后的动态配置. 路由通过 router.js 配置文件自动生成. 在 models/ menu.js中可以获取到,但从这里获取到并过滤之后的其实不是路由配置. 正确过滤方式,通过 ...
- 第十章、sys模块
目录 第十章.sys模块 第十章.sys模块 方法 详解 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.modules.keys() 返回所有已经导入的模块列表 sys.ex ...
- 【网络协议】ARP地址解析协议
地址解析协议ARP 在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址.而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址.这就导致在以太 ...
- zeus部署
1.下载zeus 阿里在github上已经不维护zeus了,在网上找到一个别人贡献的 https://github.com/michael8335/zeus2 下载下来 通过shell rz命令上传到 ...
- Change :hover CSS properties with JavaScript
I need to find a way to change CSS :hover properties using JavaScript. For example, suppose I have t ...