线程基础(二十九)

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。

前面几篇讲完了并发环境下各种集合的使用, 今天来聊一聊多线程设计模式。进入正题前显示说下,多线程的设计准则:

多线程的设计准则

1:安全性(safety)
程序正常运行的必要条件之一,对象进行逻辑操作时,对象结果状态要和设计者原意一致。举个简单例子: 多线程对ArrayList对象操作时,会得到意想不到的的结果,因为ArrayList是线程不安全的集合类,单纯的多线程操作时,不具有安全性。

2:生存性/活性(liveness)
程序正常运行的必要条件之一,无论是什么时候,必要执行的处理一定能够执行,如果死活执行不到,这个代码有问题。比如说,程序运行过程中,突然停止了,又不死机,后续代码不执行了。常见的死循环,死锁等。这种情况表示代码失去活性,多线程代码要必须强调生存性/活性。

3:可复用性(reusablility)
属于代码优化范畴,类设计成为组件,可以重复使用。编写多线程程序时,如果能巧妙的将线程的互斥机制/资源竞争机制隐藏到类中,这就是一个可复用性高的程序。

4:性能(performance)
也属于代码优化范畴,如果代码能设计成可以快速,大批量的执行,这个代码就是高性能代码。这时候就得考虑,吞吐量: 单位时间处理数量, 响应时间:开始到结束的花费时间,容量:同时进行处理的数量。

总结: 安全性, 生存性/活性是多线程设计模式的必须条件, 在满足这两个前提下,再考虑如何提高线程的复用性跟性能。

Single Threaded Execution模式

居于上面准则, 来看下本篇的主角, Single Threaded Execution模式. 这个模式是多线程设计模式中最简单,最基础的一个. 按字面意思解释 : 同一时刻某个资源只允许一个线程操作.
假设一个需求: 多个线程共同操作一个资源Resoure,先执行setData, 再执行showData方法

//竞争资源
public class Resource {
    
    
    private String data;
    public  void  setData(String data){
    
    
        this.data = data;
        try {
    
    
            Thread.sleep(1000); //放大问题
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
    public  void showData(){
    
    
        System.out.println(Thread.currentThread().getName() + "--:: data:" + data);
    }
}
public class App {
    
    
    public static void main(String[] args) {
    
    
        //共用同一个资源对象
        final  Resource resource = new Resource();
        new Thread(new Runnable() {
    
    
            public void run() {
    
    
                resource.setData("A");
                //期望:t1--:: data:A
                //结果:t1--:: data:B
                resource.showData();
            }
        }, "t1").start();
        new Thread(new Runnable() {
    
    
            public void run() {
    
    
                resource.setData("B");
                //期望:t2--:: data:B
                //结果:t2--:: data:B
                resource.showData();
            }
        }, "t2").start();
    }
}
t1--:: data:B
t2--:: data:B

看执行结果, t1线程打印出来并不是它setData进去的A, 原因: 当线程t1 通过setData方法改data=A 然后休眠1s. 紧接着线程t2进入setData方法修改data=B,并休眠. 这是t1线程休眠结束, 马上执行showData, 此时的data已经被t2改为data=B, 所以出现上面t1–:: data:B 的结果.

上述案例对resource资源不进行安全防护,就让多个线程进行操作, 最终得到非期望的结果.这就是我们常说的线程不安全的操作. 要解决这问题最简单做法就是将resource资源保护起来即可, 保证同一时刻只允许一个线程操作.

方案: synchronized 将 setData 跟 showData 方法包裹起来

public class App {
    
    
    public static void main(String[] args) {
    
    
        final  Resource resource = new Resource();
        new Thread(new Runnable() {
    
    
            public void run() {
    
    
                synchronized (resource) {
    
    
                    resource.setData("A");
                    resource.showData();
                }
            }
        }, "t1").start();
        new Thread(new Runnable() {
    
    
            public void run() {
    
    
                synchronized (resource) {
    
    
                    resource.setData("B");
                    resource.showData();
                }
            }
        }, "t2").start();
    }
}

线程t1操作前, 先获取resource资源对象锁,持有锁之后,可以执行setData/showData方法, 线程t2则必须等待线程t1释放资源对象锁才可以参与执行.

这种类型的多线程操作模式,我们称之为:Single Threaded Execution模式. 其核心点就是保证同一时刻某个资源只允许一个线程操作.

何时使用(使用场景)

1>多线程编程环境
2>多个线程同时访问同一个共享资源;
3>共享资源的状态会因线程的访问而改变;

使用注意

Single Thread Execution 模式使用不当时,可能引起死锁问题:
比如, 如果resource资源对象不是1个对象,而是多个对象呢?
假设场景: t1 先持有resource1, 再持有resource2 2个对象锁之后,才可以执行showData方法, t2更好相反.

public class App {
    
    
    public static void main(String[] args) {
    
    
        final  Resource resource1 = new Resource();
        final  Resource resource2 = new Resource();
        new Thread(new Runnable() {
    
    
            public void run() {
    
    
                synchronized (resource1) {
    
    
                    try {
    
    
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    synchronized (resource2) {
    
    
                        resource2.setData("A");
                        resource2.showData();
                    }
                }
            }
        }, "t1").start();
        new Thread(new Runnable() {
    
    
            public void run() {
    
    
                synchronized (resource2) {
    
    
                    try {
    
    
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    synchronized (resource1) {
    
    
                        resource1.setData("B");
                        resource1.showData();
                    }
                }
            }
        }, "t2").start();
    }
}

所以, 在使用Single Threaded Execution模式一定要看清楚使用场合.

猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/100079096