1-Single Thread

单线程执行模式

案例-1
背景

模拟3个人频繁地经过同一个只能容许一个人经过的门 。

(模拟三个线程调用同一个对象的方法)

当人通过门的时候,这个程序会在计数器中,递增通过的人数。另外, 还会记录通过的人的 “ 姓名与出生地”。

(当某一个线程调用该对象方法的时候,都会进行计数(使该对象的counter属性+1),另外还会记录当前对象的name和address属性,并检查name和address属性关系是否正确对应)

Main.class

​ 创建一 个门,并操作了个人不断地穿越门的 类

Gate.class

​ 表 示 门 的 类 , 当 人经 过 时 会 记 录姓 名 与 出 生 地 表示人的类,

UserThread.class

​ 只负责处理不断地在门间穿梭通过

Gate.class
class Gate{
    //表示已经通过这道门的人数
    private int counter = 0;
    //表示通过这道门的人名
    private String name = "Nobody";
    //表示通过这道门的人的出生地
    private String address = "Nowhere";
    public void pass (String name, String address) {
        this.counter++;
        this.name = name;
        this.address = address;
        check();
    }

    public String toString() {
        return "No." + counter + ": " + name + ", " + address;
    }

    /**
     * 这里为了判断人名和地址是否匹配直接看 人名 和 出生地 的首字母是否一致进行判断了
     */
    private void check(){
        if (name.charAt(0) != name.charAt(0)){
            System.out.println("********* broken *********** " + toString());
        }
    }
}
UserThread.class
class UserThread extends Thread{
        private final Gate gate;
        private final String myname;
        private final String myaddress;
        public UserThread (Gate gate, String myname, String myaddress){
            this.gate = gate;
            this.myname = myname;
            this.myaddress = myaddress;
        }

        public void run() {
            System.out.println (myname + " start");
            while (true) {
                gate.pass(myname, myaddress);
            }
        }
}
Main.class
public class Demo {

    public static void main(String[] args) {
        System.out.println("Testing Gate, hit CIRLtC to exit");
        Gate gate = new Gate();
        new UserThread(gate,"Axxxx","Alaska").start();
        new UserThread(gate,"Bxxxx","Brazil").start();
        new UserThread(gate,"Cxxxx","Canada").start();
    }

}
执行结果

请添加图片描述

分析

我们看一下这个执行的结果,每个线程开始执行的时候都会先打印一下穿过这个门的人姓名,同时我们预想中的 broken 出现了,但是好像又和预想的有点不一样。我们预想的结果是出现broken时,后面的人名和出生地不对应的,但是咱们这里出现broken时,人名和出生地大部分都是对应的,从结果来看只有一条记录是不对应的。这是为什么呢?

同时我们也能看到第一次出现broken的时候已经是第128次了,侧面的说明了有些时候仅仅靠测试是无法验证程序的安全性。

从第一条broken的打印结果来看,明明已经是姓名和出生地不匹配了,但是打印的结果是匹配的,这也从侧面说明了,在多线程的程序调试过程中,调试的结果是不可信的,值得注意。

原因分析:

在这里是以语句当作线程的基本操作单位,事实上线程可能是以更小的单位在切换的。

我们的pass方法;

public void pass (String name, String address) {
        this.counter++;
        this.name = name;
        this.address = address;
        check();
    }

pass( ) 方法里面有四条语句,每个多线程在执行的时候,可能会是交错依次执行这四条语句的。通常线程是不会去考虑其他线程的,它只会自己一直不停的跑下去。所以在这里,可能线程A刚给name赋值完,此时线程A会继续给address赋值,但是在线程A给address赋值的同时,线程B也在给name重新赋值,然后线程A去调用check( )方法时就会出现broken,当出现broken的时候线程A会继续调用toString( )方法,此时线程B又给address赋值完了,然后toString()方法打印的就会是线程B操作的name和address,线程A在进行check( )方法检查的时候,使用的是线程B的name和线程A的address进行判断的。所以就会出现我们最终在控制台打印的结果。

修改成线程安全版本

这里我们只需要加固这个门就可以实现安全问题,我们将门的pass( )和check( )方法进行线程安全处理就可以了 。


class Gate{
    //表示已经通过这道门的人数
    private int counter = 0;
    //表示通过这道门的人名
    private String name = "Nobody";
    //表示通过这道门的人的出生地
    private String address = "Nowhere";
    public synchronized void pass (String name, String address) {
        this.counter++;
        this.name = name;
        this.address = address;
        check();
    }

    public String toString() {
        return "No." + counter + ": " + name + ", " + address;
    }

    /**
     * 这里为了判断人名和地址是否匹配直接看 人名 和 出生地 的首字母是否一致进行判断了
     */
    private synchronized void check(){
        if (name.charAt(0) != name.charAt(0)){
            System.out.println("********* broken *********** " + toString());
        }
    }
}

.

在本案例中,Gate类就担任了共享资源参与者的角色。

对于pass( )和check( )这两个不安全的方法我们使用synchronized修饰符进行修饰后,就可以保证它的线程安全问题。

synchronized

synchronized修饰符并不会对程序的安全造成危害,但是调用synchronized修饰的方法时会比调用一般方法花费比较多的一些时间,所以会使程序的性能降低。

在单线程程序中使用synchronized 方法,就好像在外独居的人,即使一个人在 家中还是将厕所的门锁住的意思一样。既然是 一个人住,即使不锁门,也不需要担 心有人会突然打开厕所的门 了。

就算是多线程程序,如果所有线程完全地独立运行,那也没有使用 单线程执行模式 的必要。我们將这个状态称为线程互不干涉(interfere)。 有些管理多线程的环境,会帮我们确保线程的独立性,这种情况下这个环境的 用户就不必考虑需不需要使用单线程执行模式。

生命性与死锁

使用 单线程执行模式 时,可能会有发生死锁 (deadlock )的危险。 所谓的死锁,是指两个线程分别获取了锁定,互相等待另一个线程解除锁定的 现象。发生死锁时,哪个线程都无法继续执行下去,所以程序会失去生命性。

死锁小案例背景

假设 A 与 B 同吃一个大盘子所盛放的意大利面。盘子的旁边只有一支汤匙与一支叉子,而要吃意大利面时,同时需要用到汤匙与叉子。

只有 一支的汤匙,被 A 拿去了,两只有一支的叉子,却被 B 拿走了。 就造成以 下的情况:

  • 握着汤匙的 A ,一直等着B 把叉子放下
  • 握着叉子的 B , 一直等着A 把汤匙放下

这么一来 A 与 B 只有面面相觑,就这样不动了。像这样,多个线程僵持不下,使程序无法继续运行的状态,就称为死锁。

死锁出现条件

单线程执行达到下面这些条件时,可能会出现死锁的现象:

  • 具有多个SharedResource(共享资源) 参与者
  • 线程锁定一个SharedResource 时,还没解除前就去锁定另 一个SharedResource。
  • 获取SharedResource 参与者的顺序不固定 (和SharedResource 参与者是 对 等 的 )。

对比一下上面吃意大利面的例子:

  • 多个SharedResource 参与者,相当于汤匙与叉子。

  • 锁定某个SharedResource 参与者后,就去锁定其他SharedResource。就相当于握着汤匙而想 要获取对方的叉子,或握着叉子而想要获取对方的汤匙这些操作。

  • SharedResource角色是对等的,就像“拿汤匙一拿叉子”与“拿叉子一拿汤匙 〞两个操作都可能发生。也就是说在里汤匙与叉子并没有优先级 。

上面的三个条件只要破坏一种条件,就可以避免死锁的发生。

synchronized 在保护什么

请读者阅读程序代码的时候,看到synchronized 时,就要思考:

“这个synchronized 是在保护什么?”
无论是synchronized 方法或是synchronized块,syncbronized势必保护着“某个 东西”。

确认 “ 保护什么” 之后,接 下来应该要思考的是:

“ 其他的地方也有妥善保护到吗?”

若这些字段还在其他许多地方使用着,这里使用synchronized 小心保护,但其他地方并没有做好保护措施, 那其实这个字段还是没被保护的。就像就算小心翼翼地把大门跟后门都锁得好好的, 如果窗户敞开, 还是没有意义一样

“ 获取谁的锁定来保护的呢?,

要调用synchronized 实例方法 (instance method)的线程, 一定会获取this 的锁 定。一个实例的锁定,同一时问内只能有 一线程可以得到。因为这个惟一性,我们 才能使用synchronized 来做到单线程执行模式 如果实例不同,那锁定也不同了。虽然我们说“使用synchronized 来保护”,但 如果有多个相异实例,那多个线程仍然可以分别执行不同实例的 synchronized 方法 。

案例-2
##### 背景:

案例-1的死锁小案例简化版,汤匙和叉子,我们这里简称左手工具和右手工具,

A和B吃饭需要左手和右手配合,两只手都需要拿到对应的工具才可以吃饭。

代码:
public class Demo {

    public static void main(String[] args) {
        System.out.println("Testing Gate, hit CIRLtC to exit");
        Tools left = new Tools("left");
        Tools right = new Tools("right");
        new EatThread("A",left,right).start();
        new EatThread("B",right,left).start();
    }

}

class EatThread extends Thread{

    private String name ;
    private final Tools leftTool;

    private final Tools rightTool;

    public EatThread(String name, Tools leftTool, Tools rightTool) {
        this.name = name;
        this.leftTool = leftTool;
        this.rightTool = rightTool;
    }

    @Override
    public void run() {
        while (true){
            eat();
        }
    }

    public void eat(){
        synchronized (leftTool){//左手工具拿起
            System.out.println(name + " " + "拿到了 " + leftTool.toString());
            synchronized (rightTool){//右手工具拿起
                System.out.println(name + " " + "拿到了 " + rightTool.toString());
                System.out.println(name + "开始吃东西了========> eat");
                System.out.println(name + "放下了右手" + leftTool.toString());
            }//右手工具放下
            System.out.println(name + "放下了左手" + rightTool.toString());
        }//左手工具放下
    }

}


class Tools{
    private final String name ;

    public Tools(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name ;
    }
}
分析

上面的代码运行后会发生死锁,是因为main方法中A和B线程传参数是对称传入,这就造成了A和B拿起左右手工具时顺序不一致,就会造成A和B分别拿到一个工具,并且另一个工具都在对方手中,都在等对方放下手中的工具。

纠正
方案1

修改main方法中的A和B的传参数顺序一致,这样A和B都同时按照同一种顺序拿取工具,先拿左手工具再去拿右手工具,当左手工具没有拿到的时候就不会再去拿右手工具。

public static void main(String[] args) {
    System.out.println("Testing Gate, hit CIRLtC to exit");
    Tools left = new Tools("left");
    Tools right = new Tools("right");
    new EatThread("A",left,right).start();
    new EatThread("B",left,right).start();
}
方案2

将左右手工具放在一起,不分开,这样工具只有一个,谁拿到就是谁的,拿到了就可以直接开吃,不用再去拿第二个工具。

public static void main(String[] args) {
        System.out.println("Testing Gate, hit CIRLtC to exit");
        Tools left = new Tools("left");
        Tools right = new Tools("right");
        Pair pair = new Pair(left,right);
        new EatThread("A",pair).start();
        new EatThread("B",pair).start();
        
    }
### 回答1: create_singlethread_workqueue 函数是 Linux 内核中用来创建一个单线程工作队列的函数。 它的作用是创建一个只包含一个工作者线程的工作队列,该线程负责处理工作队列中的所有工作项。这种工作队列适合于那些只需要单个线程处理的异步任务,例如:定时器处理、任务调度等。 在调用该函数时,会返回一个指向工作队列的结构体指针,该结构体包含有关工作队列的信息。需要注意的是,在使用完工作队列后,需要调用 destroy_workqueue 函数来销毁它,以避免内存泄漏。 下面是 create_singlethread_workqueue 函数的函数原型: ``` struct workqueue_struct *create_singlethread_workqueue(const char *name); ``` 参数说明: - name:工作队列的名称,用于在系统中标识工作队列。 返回值: - 返回一个指向工作队列结构体的指针。 举例: ``` #include <linux/module.h> #include <linux/workqueue.h> static struct workqueue_struct *my_workqueue; static void my_work_handler(struct work_struct *work) { /* 处理工作项 */ } static int __init my_module_init(void) { my_workqueue = create_singlethread_workqueue("my_workqueue"); if (!my_workqueue) { printk(KERN_ERR "Failed to create workqueue\n"); return -ENOMEM; } /* 将工作项添加到工作队列 */ INIT_WORK(&my_work, my_work_handler); queue_work(my_workqueue, &my_work); return 0; } static void __exit my_module_exit(void) { /* 销毁工作队列 */ destroy_workqueue(my_workqueue); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); ``` 在这个例子中,我们首先调用 create_singlethread_workqueue 函数来创建一个名为 "my_workqueue" 的工作队列。然后,我们将一个工作项添加到队列中,并在工作者线程中处理它。最后,在模块退出时,我们调用 destroy_workqueue 函数来销毁工作队列。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值