美文网首页
线程基础(二十九)

线程基础(二十九)

作者: 叩丁狼教育 | 来源:发表于2019-08-26 17:09 被阅读0次

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

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

多线程的设计准则

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模式一定要看清楚使用场合.

相关文章

  • 线程基础(二十九)

    本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。 前面几篇讲完了并发环境下各种集合的使用, 今天来聊一...

  • Java基础

    Java基础 集合基础 集合框架 多线程基础 多线程框架 反射 代理 集合基础 ArrayList LinkedL...

  • Java-并发编程知识点总结

    目录: 线程基础 线程池 各种各样的锁 并发容器 原子类 Java 内存模型 线程协作 AQS 框架 一、线程基础...

  • 年薪50W阿里P7架构师必备知识:并发+JVM+多线程+Nett

    并发编程 线程基础、线程之间的共享和协作一 线程基础、线程之间的共享和协作二 线程的并发工具类 线程的并发工具类、...

  • Java多线程总结 之 基础概念

    基础概念 序言 线程知识结构图 线程基础 什么是线程: 线程和进程的区别: 什么是同步: 并发和并行的区别: 什么...

  • 线程-线程基础

    创建线程 new Thread(?).start() ?可以是2种。 runable接口的实现类 也可以继承Thr...

  • Android中的多线程

    1. Java多线程基础 Java多线程,线程同步,线程通讯 2. Android常用线程 HandlerThre...

  • 多线程方法

    一. 基础并行多线程结构 二. 标准多线程方法,控制并发线程数,带线程锁

  • 线程池

    线程池种类 ThreadPoolExecutor 基础线程池 线程执行任务过程 当前执行线程数 < corePoo...

  • NSThread三种创建线程的方法

    对耗时操作的理解 NSThread基础 线程控制 线程安全 线程间同行

网友评论

      本文标题:线程基础(二十九)

      本文链接:https://www.haomeiwen.com/subject/vhfdectx.html