其他分享
首页 > 其他分享> > 多线程基础---线程安全(原因及解决方法),syncronized,volatile关键字

多线程基础---线程安全(原因及解决方法),syncronized,volatile关键字

作者:互联网

目录

1. 中断线程

2. 线程等待

3. 守护线程

4. Java中的线程状态

5. 线程安全

1) 背景/原因

2) 线程安全的解决

6.  syncronized 同步关键字

7.  volatile


1. 中断线程

1) 使用标志位的方式

isStop变量.

public class FlagStop {
    private static volatile boolean isStop;

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (!isStop) {
                        System.out.println("转账....");
                        Thread.sleep(1000);
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }).start();
        //停止转账
        Thread.sleep(3000);
        isStop=true;
    }
}

2) 线程中断的API

前置: 线程Thread中,存在一个中断标志位,默认值=false(没有被中断)

   

     

      

public class InterruptThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while(!Thread.currentThread().isInterrupted()){
                        System.out.println("转账...");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
}

2. 线程等待

1)  API

线程引用对象.join()     /    线程引用对象.join(long)

当前线程阻塞等待,直到满足以下条件:

无参: 线程引用执行完毕

有参: 等待指定时间,或者线程引用执行完毕,任意一个条件满足,当前线程继续向下执行.

2) 示例

public class LearnJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    System.out.println("t start");
                    Thread.sleep(5000);
                    System.out.println("t end");
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        t.start();
        t.join(2000);
        System.out.println("main");
    }
}

3. 守护线程

1) API

参数为:

true: 设置为守护线程;

false: 设置为用户线程.

2) 一个Java进程,至少有一个用户线程存在,才会运行,否则就结束.

4. Java中的线程状态

这里的等待,超时等待,阻塞状态,和之前学习的进程状态转换图,阻塞状态,表现是一样的,线程暂停/挂起.

5. 线程安全

1) 背景/原因

代码层面上: 多个线程对同一个共享数据的操作(读,写),如果有写操作,就存在线程安全问题.

底层的原因: 原子性,可见性,有序性.

① 原子性

特殊的非原子性操作:

a) n++,n--,++n,--n

           从主存把数据读取到CPU  (读)

   进行数据的更新  (改)

   把数据写回到主存  (写回)

b) new一个对象

       

        一行new对象的代码,分解为:

        I. 分配内存空间(Java虚拟机)

       II. 执行构造方法(字节码层面的<init>构造方法: 收集成员变量,实例代码块,Java代码中的构造方法)

      III. 把new的对象赋值给变量

② 可见性

线程对主存数据的操作,需要先加载到线程私有的工作内存,操作完再写回到主存,线程之间的工作内存,是互相不可见的.

       

共享变量,存储于主存(线程共享区域),线程对变量的操作:

a) 加载到工作内存

b) 操作(赋值,修改)

c) 写回主存

③ 重排序

多行指令(字节码指令在Java虚拟机执行,及机器码指令在CPU执行)在执行时,可能进行优化(目的是提高执行效率),只是不能重排序有关联的指令.

比如洗苹果,吃苹果,这两条指令由于具有前后关系,所以就不能重排序.洗苹果,洗梨,就可以进行重排序.

2) 线程安全的解决

线程安全,是由于多个线程对共享变量的操作,并发并行执行的结果.(共享变量称为临界资源,这种代码(多行),称为临界区)

思路: 临界区代码执行时,先申请jvm加锁,然后再执行. 申请锁,是需要同一把锁来保证线程安全;申请失败,线程则需要等待(可以是阻塞式的,也可以是非阻塞式)

这种操作,在多个线程执行临界区都申请同一把锁的情况下,多个线程运行的结果,就表现为: 多个线程,依次执行临界区代码.

假如现在我(线程)----申请把餐厅101包间加锁---吃饭,喝水(临界区: 此时作为不可分割的最小执行单位)----归还锁---通知其他要在101做事的人(其他线程)---重复以上流程.

6.  syncronized 同步关键字

1) 作用:

基于对象头加锁的方式,只要申请同一把锁加锁的线程,都有同步互斥的作用.

2) 语法:

语法上对应申请锁-----加锁----释放锁

synchronized代码行: 线程申请某个对象锁

synchronized作用域结束(花括号结束): 自动释放对象锁.

3) 特别注意的事项:

多线程只有申请同一个对象的加锁,才具有同步互斥的效果.

不是对同一个对象加锁,并发并行的执行.(部分A加同一把锁,部分B不加锁,A中的多个线程同步互斥,但是B和A,B内部的多个线程,都是并发并行)

7.  volatile

1) 作用

①保证变量(分解为字节码以后的)的可见性.

②建立一个内存屏障,禁止指令重排序.

2) 使用场景:

代码行本身保证原子性的前提下,变量使用volatile修饰,可以保证线程安全.

关于代码行本身保证原子性的理解:

①读(从主存读取到线程的工作内存)

②修改为一个常量值(把常量值写回主存)

 

 

标签:加锁,Thread,对象,---,线程,new,多线程,public,syncronized
来源: https://blog.csdn.net/weixin_43939602/article/details/117441171