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知识库 -> 线程安全问题的学习 -> 正文阅读

[Java知识库]线程安全问题的学习

目录

一.有关线程安全问题

1.什么是线程安全

2.线程不安全的例子

(1)运行结果

(2)代码

(3)解释说明

3.导致线程不安全的原因

二.解决线程安全问题

1.如何解决线程安全

2.synchronized关键字(锁的学习)

(1)锁保证安全的例子

?(2)synchronized保证线程安全(保证了原子性和内存可见性)

(3)关于synchronized锁的对象和位置

?(4)注意事项

(5)了解java标准库中的线程安全和线程不安全的类

3.volatile关键字(保证内存可见性)

(1)为什么使用该关键字可以保证内存可见性

(2)没有内存可见性出现的问题以及如何解决内存可见性问题

三.线程中的wait(等待)和notify(通知)

1.wait()和notify()如何使用

2.wait的执行情况

3.notify的作用和注意事项

4.有关wait和notify的方法

5.使用wait和notify的示例:

(1)运行结果

(2)代码


一.有关线程安全问题

1.什么是线程安全

多线程并发执行某个代码时,没有产生逻辑上的错误,就是线程安全的。

2.线程不安全的例子

(1)运行结果

(2)代码

package thread.example;

public class CountThread {
    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        };
        thread1.start();
        thread2.start();
        //这里相当于让主线程等待上面执行结束再来输出count结果
        thread1.join();
        thread2.join();
        System.out.println(count);
    }
}

(3)解释说明

在代码中的++操作看起来只是执行了一次,但是实际上的++操作共有3步,为

  1. 把数据读取到CPU上
  2. 在CPU上进行++操作
  3. 最终将CPU上的新数据写回到内存中

3.导致线程不安全的原因

  1. 线程的抢占式执行。
  2. 自增操作不是非原子性。
  3. 多个线程尝试修改同一个变量
  4. 内存可见性导致的线程安全
  5. 指令重排序(JVM优化所导致的线程安全问题)

二.解决线程安全问题

1.如何解决线程安全

如果想解决线程安全问题,首先需要从导致线程安全的原因进行入手,而现在我们主要对原子性进行干预,其他的大多数还没有办法解决;为了保证原子性,我们引入了锁概念,如果一个线程没有执行完,有了锁之后,其他的线程如果需要访问,就会阻塞到那里,直到该线程放开锁,其他线程才有资格继续执行。

2.synchronized关键字(锁的学习)

解决两个线程同时写的线程安全问题

(1)锁保证安全的例子

?(2)synchronized保证线程安全(保证了原子性和内存可见性)

synchronized是一个互斥锁,只有是同一把锁,才会起到作用,否则就没有效果。而且synchronized是对指定的某个对象来进行加锁的。

代码示例(刚才上面的加法操作进行加锁):

运行结果:

代码:

package thread.example;

public class SynCountThread {
    public int count =  0;
    public  void increase() {
        synchronized (this){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynCountThread synCountThread = new SynCountThread();
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synCountThread.increase();
                }
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synCountThread.increase();
                }
            }
        };
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(synCountThread.count);
    }
}

(3)关于synchronized锁的对象和位置

1.synchronized修饰普通方法

?2.synchronized修饰类方法

?3.修饰代码块

?(4)注意事项

我们可以看出synchronized是锁的是对象,我们也可以自己new一个对象也可以作为锁进行使用。

对于没有获得锁的线程,会一直进行阻塞等待;这里阻塞等待的前提是需要线程持有的是同一把

锁,如果不是同一把锁,那么就不会产生竞争关系;从而也保证不了线程安全。

(5)了解java标准库中的线程安全和线程不安全的类

1.线程不安全的类

  • ArrayList
  • LinkedList
  • HashMap
  • TreeMap
  • HashSet
  • TreeSet
  • StringBuilder

2.线程不安全的类

  • Vector?
  • HashTable?
  • ConcurrentHashMap
  • StringBuffer
  • String(不允许修改,所以也是线程安全的)

3.volatile关键字(保证内存可见性)

?解决一个线程读,一个线程写的线程安全问题。

(1)为什么使用该关键字可以保证内存可见性

使用volatile修饰变量,目的是为了强制读写内存,因为有时候会出现当变量值发生改变后,由于没有及时的改变内存中的值,所以最终会导致与预期结果的值不同。

虽然使用了改关键字保证了内存可见性,但是保证不了原子性,对于之前的自增操作还是保证不了线程安全。

(2)没有内存可见性出现的问题以及如何解决内存可见性问题

?代码:

package thread.example;

import java.util.Scanner;

public class CanSeeThread {
//    public static int flag = 0;
    public static volatile int flag = 0;//保证了内存可见性
    public static void main(String[] args) {
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                while(flag == 0) {
                }
                System.out.println("thread1结束");
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                Scanner sc = new Scanner(System.in);
                System.out.println("请输入一个整数:");
                flag = sc.nextInt();
            }
        };
        thread1.start();
        thread2.start();
    }
}

三.线程中的wait(等待)和notify(通知)

1.wait()和notify()如何使用

首先这两个方法是Object类下的,其次wait和notify要搭配synchronized一块使用,否则直接使用会抛出异常。

2.wait的执行情况

  • 释放锁
  • 等待接收通知
  • 收到通知后重新获取锁

3.notify的作用和注意事项

通过调用指定对象的notify来使指定对象的wait结束等待,主要是用来唤醒等待的线程。

在使用notify后,当前线程不会立刻释放掉当前的锁,只有退出当前的synchronized代码块,才会释放对象锁。

4.有关wait和notify的方法

方法作用
wait()让当前对象进行等待
wait(long timeout)也是等待,只不过有时间限制
notify()唤醒一个线程的线程
notifyAll()唤醒所有的等待线程

注意:在使用wait和notify时需要注意两个synchronized代码块需要持有相同的一把锁,否则就会通知失败。

其次notifyAll()虽然是唤醒所有线程,但是锁只有一个,还是需要进行竞争,最终结果也是只有一个线程可以获得该锁,所以和notify产生的结果是一样的,一般我们不会使用notifyAll来唤醒线程。

5.使用wait和notify的示例:

(1)运行结果

(2)代码

package thread.example;

public class WaitNotify {
    public static Object locker = new Object();//创建一个锁对象
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            synchronized (locker) {
                System.out.println("wait等待");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait结束");
            }
        });
        thread.start();
        Thread.sleep(5000);
        Thread thread2 = new Thread(()->{
           synchronized (locker) {
               System.out.println("notify开始");
               locker.notify();
               System.out.println("notify结束");
           }
        });
        thread2.start();
    }
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-10 22:17:51  更:2022-03-10 22:20:12 
 
开发: 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 10:38:36-

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