Java深入探索线程安全和线程通信的特性

目录
  • 一、线程安全(重点)
    • 1、线程安全概念
    • 2、产生线程不安全的情况
    • 3、线程不安全的原因
    • 4、如何解决线程不安全问题
  • 二、synchronized关键字
    • 1、使用
    • 2、特性
  • 三、volatile关键字
    • 1、保证可见性
    • 2、禁止指令重排序
    • 3、不保证原子性
  • 四、wait和notify(线程间的通信)
    • 1、wait()方法
    • 2、notify()和notifyAll()方法
    • 3、wait和sleep的对比
  • 五、线程和进程的比较
    • 1、线程的优点
    • 2、线程和进程的区别

一、线程安全(重点)

1、线程安全概念

在多线程的情况下,需要考虑多个线程并行并发执行:此时多个线程之间的代码是随机执行的。如果多线程环境下代码的运行结果是符合我们的预期的,即在单线程情况下应该的结果,则这个程序是线程安全的。

2、产生线程不安全的情况

多个线程共享变量的操作:

  • 都是读操作时,使用值进行判断,打印等操作(不存在线程安全问题)
  • 存在线程进行了写操作(存在线程安全问题)

3、线程不安全的原因

(1)原子性:多行指令是最小的执行单位(不可拆分),就具有原子性。如果不满足原子性,则存在线程安全问题。

一条java语句不一定是原子的:

  • 如n++、n--,是有三步组成:从内存读取到cpu寄存器、修改数据、再写回到cpu;
  • Object o=new Object(),也涉及到三个步骤:申请内存、初始化对象、赋值。

以上列举的虽然只是一条java语句,但是其不具有原子性。

(2)可见性:一个线程对共享变量的修改,能够及时的被其他线程看见。多个线程并发并行执行,使用各自的工作内存,互相之间不可见(不具有可见性)。

Java内存模型(JMM):

目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现Java程序在各种平台下都能达到一致的并发效果。

  • 线程之间的共享变量存在主内存;
  • 每一个线程都有自己的工作内存;
  • 当程序要读取一个共享变量时,会先将变量从主内存拷贝到工作内存,再从工作内存读取数据;
  • 当程序要修改一个共享变量时,会先修改工作内存中的副本,再同步到主内存。

(3)代码顺序性/有序性:

代码重排序:

比如,以下代码:

1、去前台取U盘,

2、回教室写一会作业,

3、去前台取快递

在单线程的情况下,JVM、cpu会对其进行优化,1→3→2,这样会提高效率,这样就叫做代码重排序。

代码重排序的前提是:保证逻辑不发生改变。

4、如何解决线程不安全问题

设计多线程代码原则:满足线程安全的前提下,尽可能地提高效率

(1)对共享变量的写操作,可以加锁来保证线程安全:

Java中加锁的两种方式:

  • synchronized关键字:申请对给定的Java对象,对象头加锁;
  • Lock:是一个锁的接口,它的实现类提供了锁这样的对象,可以调用方法来加锁/释放锁。

对共享变量的写操作,不依赖任何共享变量,也可以使用volatile关键字来保证线程安全。

(2)对共享变量的读操作,使用volatile关键字就可以保证线程安全

volatile关键字:修饰变量,变量的读操作,本身就保证了原子性,volatile的作用是保证可见性和有序性,这样就可以保证线程安全。

二、synchronized关键字

synchronized本质上是修饰指定对象的对象头去。使用角度来看,synchronized必须搭配一个具体的对象来使用。

1、使用

(1)修饰普通方法:锁TestDemo对象

//方法一
public class TestDemo {
    public synchronized void methond() {
    }
}
//方法二
public class TestDemo {
    public  void methond() {
        synchronized(this){
        }
    }
}

(2)修饰静态方法:锁TestDemo对象

//方法一
public class TestDemo {
    public synchronized static void method() {
    }
}
//方法二
public class TestDemo {
    public static void method() {
        synchronized(TestDemo.class){
        }
    }
}

2、特性

(1)互斥

synchronized会起到同步互斥的作用,某个线程执行到某个对象的synchronized中时,如果其他线程也执行到同一个对象synchronized时会阻塞等待。

  • 进入synchronized修饰的代码块,相当于加锁;
  • 退出synchronized代码块,相当于释放锁。

互斥可以满足原子性,

(2)刷新内存

synchronized结束释放锁,会把工作内存中的数据刷新到主存中;其他线程申请锁时,获取的始终是最新的数据。(满足可见性)。

(3)有序性

某个线程执行一段同步代码,不管如何重排序,过程中不可能有其他线程执行的指令,这样多个线程执行同步代码,就满足一定的顺序。

(4)可重入

同一个线程,可以多次申请同一个对象锁(可重入)

三、volatile关键字

修饰某个变量(实例变量,静态变量)

1、保证可见性

代码在写入volatilt修饰的变量时:

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存中刷新到主存中

代码在读取volatile修饰的变量时:

  • 从主存中读取volatile变量的最新值到工作内存中
  • 从工作内存中读取副本值

2、禁止指令重排序

建立内存屏障,保证代码有序性。

3、不保证原子性

synchronized和volatile有着本质区别。synchronized可以保证原子性,volatile保证的是内存的可见性。

只能在共享变量的读操作以及常量赋值操作时使用(这些操作本身就具有原子性)

四、wait和notify(线程间的通信)

线程通信:线程间通信,就是一个线程以通知的方式,唤醒某些等待的线程(或者让当前线程等待),这样就可以让线程通过通信的方式具有一定的顺序性。

1、wait()方法

wait做的事情:

  • 使当前执行代码的线程进入等待状态(把线程放到等待队列中)
  • 释放当前的锁
  • 满足一定条件时,重新尝试获取这个锁

wait结束等待的条件:

  • 其他线程调用该对象的notify方法
  • wait等待时间超时(wait提供了一个带一个参数的方法,可以指定等待时间)
  • 其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException异常

wait要搭配synchronized来使用。脱离synchronized使用wait会直接抛异常。

如下:

Object object = new Object();
    synchronized (object) {
        object.wait();
    }
//这种情况下线程会一直等待下去,这个时候需要使用notify来唤醒

2、notify()和notifyAll()方法

notify方法只是唤醒某一个等待的线程,使用notifyAll方法可以一次性唤醒所有的等待线程。

  • notify()也是在同步方法中调用,用来通知其他等待的线程,对其发出通知notify,并使它们重新获取该对象的对象锁;
  • 如果有多个线程在等待,则有线程调度器随机挑选出一个处于等待状态的线程;
  • notify()方法执行后,当前线程不会立马释放该对象锁,要等到当前线程将程序执行完,退出同步代码块后才会释放对象锁。

【注】

虽然notifyAll()同时唤醒所有处于等待状态的线程,但是这些线程需要竞争锁。所以并不是同时执行,仍然是有先后顺序的执行。

3、wait和sleep的对比

一个是用于线程之间的通信,一个是让线程阻塞一段时间。

  • wait需要搭配synchrionzed使用,sleep不用
  • wait是Object的方法,sleep是Thread的静态方法

五、线程和进程的比较

1、线程的优点

  1. 创建线程的代价比创建进程小得多
  2. 与进程切换相比,线程切换需要操作系统做的事少得多
  3. 线程占用的资源比进程少
  4. 能充分利用多个处理器,提高效率
  5. 在等待I/O操作结束的同时,程序可执行其他的计算任务
  6. I/O密集型操作,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
  7. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

2、线程和进程的区别

  • 进程是进行资源分配的最小单位,线程是程序执行的最小单位
  • 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈
  • 同一个进程的各线程之间共享内存和文件资源,可以不通过内核进行直接通信
  • 线程的创建、切换、销毁效率更高

到此这篇关于Java深入探索线程安全和线程通信的特性的文章就介绍到这了,更多相关Java线程安全内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解java线程通信

    前言 开发中不免会遇到需要所有子线程执行完毕通知主线程处理某些逻辑的场景. 或者是线程 A 在执行到某个条件通知线程 B 执行某个操作. 可以通过以下几种方式实现: 等待通知机制 等待通知模式是 Java 中比较经典的线程通信方式. 两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯. 如两个线程交替打印奇偶数: public class TwoThreadWaitNotify { private int start = 1; private boolean

  • Java并发编程之线程安全性

    目录 1.什么是线程安全性 2.原子性 2.1 竞争条件 2.2 复合操作 3.加锁机制 3.1 内置锁 3.2 重入 4.用锁保护状态 5.活跃性与性能 1.什么是线程安全性 当多个线程访问某个类时,不管运行时环境采用何种调用方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 无状态的对象一定是线程安全的,比如:Servlet. 2.原子性 2.1 竞争条件 由于不恰当的执行时序而出现不正确的结果的情况,就是竞争

  • Java多线程之线程安全问题详解

    目录 1.什么是线程安全和线程不安全? 2.自增运算为什么不是线程安全的? 3.临界区资源和竞态条件 总结: 面试题: 什么是线程安全和线程不安全? 自增运算是不是线程安全的?如何保证多线程下 i++ 结果正确? 1. 什么是线程安全和线程不安全? 什么是线程安全呢?当多个线程并发访问某个Java对象时,无论系统如何调度这些线程,也无论这些线程将如何交替操作,这个对象都能表现出一致的.正确的行为,那么对这个对象的操作是线程安全的. 如果这个对象表现出不一致的.错误的行为,那么对这个对象的操作不是

  • Java线程通信中关于生产者与消费者案例分析

    相关方法: wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器. notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个. notifyAll():一旦执行此方法,就会唤醒所有被wait的线程. 说明: 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中. 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器.

  • Java使用线程同步解决线程安全问题详解

    第一种方法:同步代码块: 作用:把出现线程安全的核心代码上锁 原理:每次只能一个线程进入,执行完毕后自行解锁,其他线程才能进来执行 锁对象要求:理论上,锁对象只要对于当前同时执行的线程是同一个对象即可 缺点:会干扰其他无关线程的执行 所以,这种只是理论上的,了解即可,现实中并不会这样用 public class 多线程_4线程同步 { public static void main(String[] args) { //定义线程类,创建一个共享的账户对象 account a=new accoun

  • Java多线程通信问题深入了解

    目录 概述 引入 加入线程安全 实现生产者与消费者问题 总结 概述 多线程通信问题,也就是生产者与消费者问题 生产者和消费者为两个线程,两个线程在运行过程中交替睡眠,生产者在生产时消费者没有在消费,消费者在消费时生产者没有在生产,确保数据安全 以下为百度百科对于该问题的解释: 生产者与消费者问题: 生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例.该问题描述了两个共享固定大小缓

  • 深入理解Java 线程通信

    当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但 Java 也提供了一些机制来保证线程协调运行. 传统的线程通信 假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者--现在假设系统有一种特殊的要求,系统要求存款者和取钱者不断地重复存款.取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱.不允许存款者连续两次存钱,也不允许取钱者连续两次取钱. 为了实现这种功能,可以借助于 Object 类提供的 wait(). notify()

  • Java线程安全问题的解决方案

    目录 线程安全问题演示 解决线程安全问题 1.原子类AtomicInteger 2.加锁排队执行 2.1 同步锁synchronized 2.2 可重入锁ReentrantLock 3.线程本地变量ThreadLocal 总结 前言: 线程安全是指某个方法或某段代码,在多线程中能够正确的执行,不会出现数据不一致或数据污染的情况,我们把这样的程序称之为线程安全的,反之则为非线程安全的.在 Java 中, 解决线程安全问题有以下 3 种手段: 使用线程安全类,比如 AtomicInteger. 加锁

  • JAVA 线程通信相关知识汇总

    两个线程之间的通信 多线程环境下CPU会随机的在线程之间进行切换,如果想让两个线程有规律的去执行,那就需要两个线程之间进行通信,在Object类中的两个方法wait和notify可以实现通信. wait方法可以使当前线程进入到等待状态,在没有被唤醒的情况下,线程会一直保持等待状态. notify方法可以随机唤醒单个在等待状态下的线程. 来实现这样的一个功能: 让两个线程交替在控制台输出一行文字 定义一个Print类,有两个方法print1和print2,分别打印一行不同的内容 package c

  • Java深入探索线程安全和线程通信的特性

    目录 一.线程安全(重点) 1.线程安全概念 2.产生线程不安全的情况 3.线程不安全的原因 4.如何解决线程不安全问题 二.synchronized关键字 1.使用 2.特性 三.volatile关键字 1.保证可见性 2.禁止指令重排序 3.不保证原子性 四.wait和notify(线程间的通信) 1.wait()方法 2.notify()和notifyAll()方法 3.wait和sleep的对比 五.线程和进程的比较 1.线程的优点 2.线程和进程的区别 一.线程安全(重点) 1.线程安

  • Java编程之多线程死锁与线程间通信简单实现代码

    死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源:我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行的时候,A还需要LockB进行下面的操作,这个时候b资源在被B线程操作,刚好被上了锁LockB,假如此时线程B刚好释放了LockB则没有问题,但没有释放LockB锁的时候,线程A和B形成了对LockB锁资源的争夺,从而造成阻塞,形成死锁:具体其死锁代码如下:

  • 深入解析Java的线程同步以及线程间通信

    Java线程同步 当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用.达到此目的的过程叫做同步(synchronization).像你所看到的,Java为此提供了独特的,语言水平上的支持. 同步的关键是管程(也叫信号量semaphore)的概念.管程是一个互斥独占锁定的对象,或称互斥体(mutex).在给定的时间,仅有一个线程可以获得管程.当一个线程需要锁定,它必须进入管程.所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程.这些其他的线程被

  • Java编程中实现Condition控制线程通信

    java中控制线程通信的方法 1.传统的方式:利用synchronized关键字来保证同步,结合wait(),notify(),notifyAll()控制线程通信.不灵活. 2.利用Condition控制线程通信,灵活. 3.利用管道pipe进行线程通信,不推荐 4.利用BlockingQueue控制线程通信 本文就讲解利用Condition控制线程通信,非常灵活的方式. Condition类是用来保持Lock对象的协调调用. 对Lock不了解的可以参考:Java线程同步Lock同步锁代码示例

  • Android中子线程和UI线程通信详解

    Android中子线程和UI线程之间通信的详细解释 1.在多线程编程这块,我们经常要使用Handler,Thread和Runnable这三个类,那么他们之间的关系你是否弄清楚了呢?下面详解一下. 2.首先在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行. 3.Handler: (1).概念: Handler是沟通Activity 与Thread/runnable的桥梁.而Handler是运行在主UI线程中的,它与子线程

  • linux多线程编程详解教程(线程通过信号量实现通信代码)

    线程分类 线程按照其调度者可以分为用户级线程和核心级线程两种. (1)用户级线程 用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持.在这里,操作系统往往会提供一个用户空间的线程库,该线程库提供了线程的创建.调度.撤销等功能,而内核仍然仅对进程进行管理.如果一个进程中的某一个线程调用了一个阻塞的系统调用,那么该进程包括该进程中的其他所有线程也同时被阻塞.这种用户级线程的主要缺点是在一个进程中的多个线程的调度中无法发挥多处理器的优势.

  • Java concurrency线程池之线程池原理(一)_动力节点Java学院整理

    ThreadPoolExecutor简介 ThreadPoolExecutor是线程池类.对于线程池,可以通俗的将它理解为"存放一定数量线程的一个线程集合.线程池允许若个线程同时允许,允许同时运行的线程数量就是线程池的容量:当添加的到线程池中的线程超过它的容量时,会有一部分线程阻塞等待.线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理." ThreadPoolExecutor数据结构 ThreadPoolExecutor的数据结构如下图所示: 各个数据在Thread

  • Java concurrency线程池之线程池原理(二)_动力节点Java学院整理

    线程池示例 在分析线程池之前,先看一个简单的线程池示例. import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class ThreadPoolDemo1 { public static void main(String[] args) { // 创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newFixedThre

  • Java线程之守护线程(Daemon)用法实例

    本文实例讲述了Java线程之守护线程(Daemon)用法.分享给大家供大家参考.具体如下: 守护线程(Daemon) Java有两种Thread:"守护线程Daemon"与"用户线程User". 我们之前看到的例子都是用户,守护线程是一种"在后台提供通用性支持"的线程,它并不属于程序本体. 从字面上我们很容易将守护线程理解成是由虚拟机(virtual machine)在内部创建的,而用户线程则是自己所创建的.事实并不是这样,任何线程都可以是&qu

  • Java 集合中的类关于线程安全

    Java集合中那些类是线程安全的 线程安全类 在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的.在jdk1.2之后,就出现许许多多非线程安全的类. 下面是这些线程安全的同步的类: vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用.在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的. statck:堆栈类,先进后出 hashtable:就比hashmap多了个线程安全 enumeration:枚举,相当于迭代器

随机推荐