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多线程系列二)线程间同步的更多相关文章

  1. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  2. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  3. Java多线程系列--“JUC线程池”04之 线程池原理(三)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...

  4. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

  5. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

  6. java多线程系列(二)

    对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

  7. java多线程系列(二)---对象变量并发访问

    对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

  8. java多线程系列(六)---线程池原理及其使用

    线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...

  9. Java多线程系列--“JUC线程池”01之 线程池架构

    概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构 ...

随机推荐

  1. docker CMD 和 ENTRYPOINT 区别

    昨天用Dockerfile来启动mongodb的集群,启动参数--replSet死活没执行,最后就决定研究一哈cmd和entrypoint.但是上网看了一些资料个人觉得讲的不好,还是没有说出根本的东西 ...

  2. linux常用的bash指令

    文本处理 awk sed grep sort uniq cat cut echo fmt tr nl egrep fgrep wc 进程监视 ps top htop atop lsof 网络 nmap ...

  3. 记项目管理大作业Web项目Mandrian的全流程[其一] 整体分析: 功能划分, 组织结构

    Mandrian是个图书管理系统, 具体需求老师给出 这个项目的目的主要是管理过程和高层设计的学习和实践 11人小组, 路人局 成员调查 这里由于很多人我都不认识, 所以我提前发了一个能力调查表, 调 ...

  4. javascript&jquery方法比对

    参考链接:https://juejin.im/post/5d2705d8e51d4577407b1dda 参考评论链接http://youmightnotneedjquery.com/ javascr ...

  5. 【Java】 BIO与NIO以及AIO分析

    一.BIO与NIO以及AIO的概念 BIO是同步阻塞式的IO NIO是同步非阻塞的IO (NIO1.0,JDK1.4) AIO是非同步非阻塞的IO(NIO2.0,JDK1.7) 二.BIO简单分析 1 ...

  6. umi+antdpro 2.3

    关于umi接管了路由之后的动态配置. 路由通过 router.js 配置文件自动生成. 在 models/ menu.js中可以获取到,但从这里获取到并过滤之后的其实不是路由配置. 正确过滤方式,通过 ...

  7. 第十章、sys模块

    目录 第十章.sys模块 第十章.sys模块 方法 详解 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.modules.keys() 返回所有已经导入的模块列表 sys.ex ...

  8. 【网络协议】ARP地址解析协议

    地址解析协议ARP 在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址.而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址.这就导致在以太 ...

  9. zeus部署

    1.下载zeus 阿里在github上已经不维护zeus了,在网上找到一个别人贡献的 https://github.com/michael8335/zeus2 下载下来 通过shell rz命令上传到 ...

  10. 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 ...