使用线程的场景 - 线程并发安全的产生原因以及解决方法

当我们买电脑时,常常会格外关注电脑的cpu有几核,几线程。因为这往往决定了电脑的运行速度

,而这一切可以在任务管理器中找到。

这时,尽管已经接触了多线程操作,也许你会对线程到底是什么产生疑问。

根据百度百科:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

那么我们什么时候需要用到多线程呢?

1 :同时处理多个不同任务
例如视频会议系统:
1 :传输音频
2 :传输视频
3 :共享桌面
2 :同时处理多个相同的任务(又称为 并发式
小球 5000
1 : 一个线程轮询所有小球 绘制 移动等操作
当球的数量越来越多的情况: 轮询耗时长
多线程来处理这些球:
单个线程只负责一部分: 比如说一个线程只运行一千个
3: 流水线模式
一个完整的任务链其中有
任务 1
任务 2 :需要任务 1 完成
任务 3 :需要任务 2 完成

这样做可以使每个线程最高效利用起来。

那么线程并发安全是什么呢?

处理并发式任务时,由于多个线程操作同一资源,操作时间并未错开,而使得内存中的操作重复,从而数据错乱,造成安全问题。

扫描二维码关注公众号,回复: 14796812 查看本文章

 例如:我们给每个线程都安排了任务,即对num加1000次。

线程栈操作步骤:从堆内存得到num——>>复制给线程栈的num副本——>>进行num++操作

——>>将num返回,赋值给堆内存中的num。

这样看来是不是井然有序,但是我们值得注意到并不一定同时只有一个线程从堆内存中得到num。

如果出现几个线程得到时,num++操作便会重复进行,使得最后少于3000次++,而数据错误。

 常见情况:

1 :读读 不会产生线程安全问题
2: 读写 会产生
3 :写写 会产生

而我们该如何解决这个问题呢?

毕竟如果这份数据代表的是你的银行金额,一定不会想要它出现错误吧!!!

利用锁(synchronized)

同步代码块,锁住资源,只让当前持有监视器锁的线程访问资源

可以理解为:当一个线程复制了堆内存中的num时,堆内存暂时被“上锁”,无法访问。

只有当线程完成了num的操作时,将num赋值给堆内存时,才会“开锁”,使得其他线程继续竞争访问堆内存。

这样既能保证了操作不会重复,数据不会错误,同时由于竞争访问机制,使得该任务速度大大加快。

例子如下:

Task类:

public class Task{

     static int num=0;//静态变量,处于堆内存中

    public static void main(String[] args) {

        Task tt = new Task();
        Thread t1=new Thread(new Threadsy(tt));
        t1.start();
        Thread t2=new Thread(new Threadsy(tt));
        t2.start();
        Thread t3=new Thread(new Threadsy(tt));
        t3.start();//创建并启动三个线程

        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            throw new RuntimeException (e);
        }//阻塞,直到线程全部完成,便于得到最终结果

        System.out.println(tt.num);//检验结果
        
    }
}

Threadsy类:

public class Threadsy implements Runnable {

    Task tt = new Task();
    public Threadsy(Task tt) {
        this.tt = tt;
    }//构析方法,便于取堆内存中的num。

    static Object object = new Object();//值得注意的是,Object要保证是静态变量,存在堆内存中,才可以通过它“锁住”堆内存的num

    public void run() {
            for (int i = 0; i < 1000; i++) {

                synchronized (object/*只能“锁”对象*/) {
                    tt.num++;
                }//给堆内存上锁,保证线程安全

        }
    }//线程操作方式
}

结果都为:

一切正常!!!

而如果我们去掉了资源锁:

数据错误:

并发式任务时,使用多线程能大大提高效率,但是要十分注意线程安全问题!

猜你喜欢

转载自blog.csdn.net/AkinanCZ/article/details/126058188