IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【多线程系列】Synchronized 和 Volatile关键字 -> 正文阅读

[Java知识库]【多线程系列】Synchronized 和 Volatile关键字

一、了解Synchronized关键字嘛?简单说说~

Synchronized关键字,表示对当前的普通方法/静态方法/代码块进行加锁,当代码运行至该方法时,要检查有没有其他线程正在用这个方法,有的话要等别d的线程用完之后再锁定调用,接着执行代码。synchronized有三个特性,分别是:
1.互斥
某个线程执行到某个对象的synchronized时,若其他线程也需执行同一个对象的synchronized,此时就会阻塞等待。
进入Synchronized修饰的代码块,即加锁;退出Synchronized修饰的代码块,即解锁;
Synchronized的使用:
① 直接修饰普通方法(锁的是SynchronizedDemo 类的实例

public class SynchronizedDemo {
	public synchronized void methond() {
	}
}

② 修饰静态方法(针对 类对象 进行加锁)

public class SynchronizedDemo {
	public synchronized static void methond() {
	}
}

③ 修饰代码块(明确指定锁哪个对象)

public class SynchronizedDemo {
	public void methond() {
		synchronized(this){}  //针对this加锁,锁当前对象
		synchronized(SynchronizedDemo.class){}  //针对类对象进行加锁
	}
}

我们要理解的是:当两个线程竞争同一把锁时,才会产生阻塞等待。

举个栗子?:加锁的这个过程就类似于ATM取钱,一个人进去了之后其他人只能在门口等待,而且这个人出来之后,大家都有机会进去ATM取钱。线程之间是抢占式执行的,所有线程执行的先后顺序不确定。

理解阻塞等待:针对每一把锁,操作系统内部维护了一个等待队列,如果有线程使用了这把锁,其他线程只能阻塞等待,一直等待该锁释放后,由操作系统唤醒一个新的线程再来获取该锁。
注意:下一个线程并不是立刻就能获取到锁,要靠操作系统进行调度(唤醒)。后来的这些线程还要进行锁的竞争,并不存在“先来后到”的原则。

2.保证可见性(及时刷新内存)
编译器会优化类似于i++这样的代码,为了提高效率,会将一些中间的加载和保存过程省略掉,加上synchronized关键字之后,就会禁止上面的优化流程,保证每次进行修改删除等操作时都会把数据真正同步至内存。

可见性:线程在工作内存中修改变量的值能够及时的同步至主内存中。
synchronized 能够及时的刷新内存,保证了内存的可见性。 具体工作过程如下:
①获得互斥锁
②从主内存拷贝变量的最新副本到工作内存
③执行代码
④将更改后的共享变量的值刷新到主内存
⑤释放互斥锁

3.可重入锁
Synchronized允许一个线程针对同一把锁连续加锁多次(可重入),所以不会出现死锁这样的情况。
这是因为:可重入锁的内部维护了 “线程持有者” 和 “计数器” 这两个属性。
如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增。
解锁的时候计数器递减为 0 的时候, 才真正释放锁 (才能被别的线程获取到)。

二、Volatile关键字呢?它们两者有何区别?

① volatile能够保证内存的可见性,但是不能保证原子性。synchronized既能保证内存可见性,又能保证原子性。这是它们最本质的区别!
② volatile仅能使用在 变量 级别;synchronized则可以使用在变量、方法、和类级别的。
③ volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
④ volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
在JMM中,每个线程都有自己独立的工作内存,即一组寄存器/cache高速缓冲区;在CPU和内存交互的时候,会先把内存中内容拷贝到工作内存,操作完之后在写回内存,这个过程非常容易出现数据不一致的情况,在编译器开启优化时特别严重。

三、wait()和notify()方法介绍一下?具体如何使用?

线程之间是抢占式执行的,先后顺序无法确定,依赖于操作系统的调度。在实际开发中,我们希望合理的协调多个线程之间的先后执行顺序。就有wait()、notify()/notifyAll()方法,它们都是Object类的方法。

wait()方法:让线程进入等待
wait()方法要做的事情:(wait() 要和synchronized搭配使用,否则会抛出异常

  • 让当前执行的线程进入阻塞等待状态
  • 释放当前的锁,让其它线程进行操作
  • 满足一定条件时会被操作系统唤醒,再次尝试获取当前锁

wait()结束等待的条件

  • 其他线程调用该对象的notify()方法
  • wait等待时间超时,重新尝试获取锁
  • 其他线程调用该等待线程的interrupted()方法,导致wait抛出InterruptedException异常

notify()方法:唤醒线程

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

notifyAll():notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程,但是这些线程还需要竞争锁。

//代码理解wait()和notify()
public class testDemo {

    static class WaitTask implements Runnable{
        private Object locker;  //针对同一个锁对象尝试加锁 

        public WaitTask(Object locker) {
            this.locker = locker;
        }

        @Override
        public void run() {
            synchronized (locker){
                try{
                    System.out.println("start");
                    locker.wait();  
                    System.out.println("end");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    static class NotifyTask implements Runnable{
        private Object locker;

        public NotifyTask(Object locker) {
            this.locker = locker;
        }

        @Override
        public void run() {
            synchronized (locker){
                System.out.println("start");
                locker.notify();  
                System.out.println("end");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new NotifyTask(locker));
        t1.start();
        Thread.sleep(1000);   //处理异常InterruptedException 
        t2.start();
    }
}

四、wait()和sleep()方法对比?

wait()方法用于线程之间的通信,可以指定时间,也可以无限等待;sleep()可以让线程阻塞一段时间;
wait()要搭配synchronized使用,sleep()不需要;
wait()是Object的方法,sleep()是Thread的静态方法;
wait()唤醒可以通过notify()/interrupt()/时间到来唤醒,sleep()可以通过时间到/interrupt();
wait()主要用于协调线程之间的先后顺序,sleep单纯让线程休眠,并不涉及到多个线程的配合。

如有错误,请批评指正!

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:27:33  更:2022-04-18 17:30:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:04:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码