JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

Java基础——Java多线程(什么是线程安全?)

wys521 2025-03-03 20:40:15 精选教程 11 ℃ 0 评论

1 基本概括

2 主要介绍

2.1 线程安全的概念

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

2.2 多线程编程的三个概念

2.2.1 原子性

这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

2.2.2 可见性

这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

2.2.3 有序性

顺序性指的是,程序执行的顺序按照代码的先后顺序执行。

以下面这段代码为例

boolean started = false; // 语句1

long counter = 0L; // 语句2

counter = 1; // 语句3

started = true; // 语句4

从代码顺序上看,上面四条语句应该依次执行,但实际上JVM真正在执行这段代码时,并不保证它们一定完全按照此顺序执行。

处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。

2.3 解决机制

1、加锁:

  a、锁能使其保护的代码以串行的形式来访问,当给一个复合操作加锁后,能使其成为原子操作。一种错误的思想是只要对写数据的方法加锁,其实这是错的,对数据进行操作的所有方法都需加锁,不管是读还是写

  b、加锁时需要考虑性能问题,不能总是一味地给整个方法加锁synchronized就了事了,应该将方法中不影响共享状态且执行时间比较长的代码分离出去

  c、加锁的含义不仅仅局限于互斥,还包括可见性。为了确保所有线程都能看见最新值,读操作和写操作必须使用同样的锁对象

2、不共享状态:

  无状态对象: 无状态对象一定是线程安全的,因为不会影响到其他线程

  线程关闭: 仅在单线程环境下使用

3、不可变对象:

  可以使用final修饰的对象保证线程安全,由于final修饰的引用型变量(除String外)不可变是指引用不可变,但其指向的对象是可变的,所以此类必须安全发布,也即不能对外提供可以修改final对象的接口

2.4 线程安全的级别

线程安全的级别或者粒度有三种,如下:

(1)线程安全

这种情况下其实没有线程安全问题,比如上面的例子中,每个人都有自己专用的卫生间,所以不会存在竞争问题。

(2)条件安全

条件安全,顾名思义是有条件的,所有人共用几个卫生间,抢到资源的就把门关上,通过门来隔离资源,后面的人就在外面等待直到里面的人出来。

(3)不安全

这种情况下连门都没有,所以并不能很好保证资源安全,所以这种情况***不能让同时让多个人直接使用。

2.5 并发的概念

并发 指单个cpu同时处理多个线程任务,cpu在反复切换任务线程,实际还是串行化的;

并行 指多个cpu同时处理多个线程任务,cpu可以同时处理不同的任务,异步处理;

并发条件

第一,是否有共享变量 第二,是否多线程环境 第三,是否多个线程更新共享变量 一句话:多个线程操作同一个对象;

2.5 并发的优势和风险

2.6 避免并发

2.6.1 线程封闭

什么是线程封闭?

就是把对象封装到一个线程里,只有这一个线程能看到此对象。那么这个对象就算不是线程安全的也不会出现任何安全问题。

实现线程封闭有哪些方法?

ad-hoc 线程封闭

这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现。

Ad-hoc 线程封闭非常脆弱,应该尽量避免使用。

栈封闭

栈封闭是我们编程当中遇到的最多的线程封闭。

什么是栈封闭呢?

简单的说就是局部变量。

多个线程访问一个方法,此方法中的局部变量都会被拷贝一份到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

2.6.2 无状态的类

没有任何成员变量的类,就叫无状态的类,这种类一定是线程安全的。

无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,是不变类。

2.6.3 让类不可变

让状态不可变,两种方式:

1、 加 final 关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上 final 关键字,但是加上 final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。

2、根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值。

2.6.4 volatile

并不能保证类的线程安全性,只能保证类的可见性,最适合一个线程写,多个线程读的情景。

2.6.5 加锁和CAS

我们最常使用的保证线程安全的手段,使用 synchronized 关键字,使用显式锁,使用各种原子变量,修改数据时使用 CAS 机制等等。

2.6.6 安全的发布

类中持有的成员变量,如果是基本类型,发布出去,并没有关系,因为发布出去的其实是这个变量的一个副本。

但是如果类中持有的成员变量是对象的引用,如果这个成员对象不是线程安全的,通过 get 等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。

2.6.7 ThreadLocal

ThreadLocal 是实现线程封闭的最好方法。

ThreadLocal 内部维护了一个 Map,Map 的 key 是每个线程的名称,而 Map 的值就是我们要封闭的对象。每个线程中的对象都对应着 Map 中一个值,也就是 ThreadLocal 利用 Map 实现了对象的线程封闭。

2.7 实现线程安全的方式

2.7.1 synchronized(自动锁,锁的创建和释放都是自动的)

synchronized(同一个锁){ //可能会发生线程冲突问题 }

锁的释放是在synchronized同步代码执行完毕后自动释放。

同步的前提:

1,必须要有两个或者两个以上的线程 ,如果小于2个线程,则没有用,且还会消耗性能(获取锁,释放锁)

2,必须是多个线程使用同一个锁

弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。

2.7.2 lock 手动锁(手动指定锁的创建和释放)

可以视为synchronized的增强版,提供了更灵活的功能。该接口提供了限时锁等待、锁中断、锁尝试等功能。synchronized实现的同步代码块,它的锁是自动加的,且当执行完同步代码块或者抛出异常后,锁的释放也是自动的。

2.7.3 volatile关键字

 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  2)禁止进行指令重排序。

synchronized、volatile和Lock之间的区别

synochronizd和volatile关键字区别:

1)volatile关键字解决的是变量在多个线程之间的可见性;而sychronized关键字解决的是多个线程之间访问共享资源的同步性。

tip: final关键字也能实现可见性:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把 “this”的引用传递出去(this引用逃逸是一件很危险的事情,其它线程有可能通过这个引用访问到了"初始化一半"的对象),那在其他线程中就能看见final;

2)volatile只能用于修饰变量,而synchronized可以修饰方法,以及代码块。(volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且随着JDK新版本的发布,sychronized关键字在执行上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大);

3)多线程访问volatile不会发生阻塞,而sychronized会出现阻塞;

4)volatile能保证变量在多个线程之间的可见性,但不能保证原子性;而sychronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。

线程安全包含原子性可见性两个方面。

对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。

一句话说明volatile的作用:实现变量在多个线程之间的可见性。

synchronized和lock区别:

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率(读写锁)。

3 使用线程安全的简单用例

3.1 同步代码块

public class ThreadSafeProblem {
    public static void main(String[] args) {
        Consumer abc = new Consumer();
        // 注意要使用同一个abc变量作为thread的参数,
        // 如果你使用了两个Consumer对象,那么就不会共享ticket了,就自然不会出现线程安全问题
        new Thread(abc,"窗口1").start();
        new Thread(abc,"窗口2").start();
    }
}
class Consumer implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            synchronized (Consumer.class) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售卖第" + (100-ticket+1) + "张票");
                    ticket--;
                }
            }
        }
    }
}


3.2 Lock锁是需要手动去加锁和释放

/*
 * 使用ReentrantLock类实现同步
 * */
class MyReenrantLock implements Runnable{
    //向上转型
    private Lock lock = new ReentrantLock();
    public void run() {
        //上锁
        lock.lock();
        for(int i = 0; i < 5; i++) {
            System.out.println("当前线程名: "+ Thread.currentThread().getName()+" ,i = "+i);
        }
        //释放锁
        lock.unlock();
    }
}
public class MyLock {
    public static void main(String[] args) {
        MyReenrantLock myReenrantLock =  new MyReenrantLock();
        Thread thread1 = new Thread(myReenrantLock);
        Thread thread2 = new Thread(myReenrantLock);
        Thread thread3 = new Thread(myReenrantLock);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}


3.3 volatile关键字

public class Singleton3 {
    private static volatile Singleton3 instance = null;
    private Singleton3() {}
    public static Singleton3 getInstance() {
        if (instance == null) {
            synchronized(Singleton3.class) {
                if (instance == null)
                    instance = new Singleton3();
            }
        }
        return instance;
    }
}



4 常见的问题研究

1. 什么叫线程安全?servlet是线程安全吗?
2. 同步有几种实现方法?
3.volatile有什么用?能否用一句话说明下volatile的应用场景?
4.?请说明下java的内存模型及其工作流程。
5. 何解决多线程之间线程安全问题?
6.为什么会有线程安全问题?
7.多线程死锁?
8.说一说自己对于 synchronized 关键字的了解
9. 怎么使用 synchronized 关键字
10. 讲一下 synchronized 关键字的底层原理
11.说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
12. 谈谈synchronized和ReenTrantLock 的区别
13. synchronized 关键字和 volatile 关键字的区别 

常见出现的问题会在后面的文章讨论,想一起讨论学习的朋友可以点下关注,会持续更新,文章有帮助的话可以收藏转发,有什么补充可以在下面评论,谢谢!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表