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. NYOJ 石子合并(一) 区间dp入门级别

    描述    有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆.合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆.求出总的代价 ...

  2. C#面向对象12 集合

    ArrayList和HashTable集合 1.ArrayList集合 ***添加元素 using System; using System.Collections.Generic; using Sy ...

  3. workerman stop失败

    1.官网说明:https://www.bookstack.cn/read/workerman-manual/faq-stop-fail.md 2.workerman.pid 中的有一个pid号,置为空 ...

  4. C#异步编程学习笔记之-async和await

    一.异步方法介绍(async和await):如果使用async修饰符将某种方法指定为异步方法,即启用以下两种功能.1.标记的异步方法可以使用await来指定暂停点.await运算符通知编译器异步方法: ...

  5. 无障碍开发(六)之ARIA在HTML中的使用规则

    ARIA使用规则一 如果你使用的元素( HTML5 )具有语义化,应该使用这些元素,而不应该重新定义一个添加ARIA的角色.状态或属性的元素. 浏览器的语义化标签已经默认隐含ARIA语义,像nav,a ...

  6. mqtt协议实现 java服务端推送功能(一)安装

    最近有个新需求,需要通过java服务端把信息推送到mqtt服务器上,安卓和ios端从mqtt服务器上获取信息实现推送. 1. 本地需要安装Mosquitto服务器  http://mosquitto. ...

  7. requests 抓取网站

    import requests from requests.exceptions import RequestException import re import json def get_one_p ...

  8. 闭包问题for(var i=0;i<10;i++){ setTimeout(function(){ console.log(i)//10个10 },1000) }

    for(var i=0;i<10;i++){ setTimeout(function(){ console.log(i)//10个10 },1000) } 遇到这种问题 如何用解决呢 for(v ...

  9. Pycharm开发环境设置与熟悉

    Pycharm开发环境设置与熟悉. 练习基本输入输出: print('你好,{}.'.format(name)) uprint(sys.argv) 库的使用方法: import ... from .. ...

  10. js页面加载时候的调用函数的方法

    方法一:jquery 中:$(function(){}) 括号内写你的内容 方法二:html <body onload=''> <script type="text/jav ...