`
flyPig
  • 浏览: 139858 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

多线程常用模式总结一

    博客分类:
  • Java
阅读更多
线程同步
线程同步其实就是线程排队访问资源。只有访问共享变量的情况下才需要同步。其一,要是共享的资源,比如一个文件,一个内存变量。其二,要是变量,如果是常量就不需要同步了。

线程锁定
同步的实现方法,就是线程锁定。线程访问共享资源,此线程即持有锁,如果此线程不release这个锁,其他线程就无法访问。锁定通过synchronized或者volatile关键字来实现。如果是对代码段套上synchronized,则表示所有线程在执行到这段代码的时候,必须排队
如果给方法加上,就表示所有线程执行到此方法都得排队。
如果是对static方法加上synchronized, 就对这个类的所有实例对象起作用。
如果是对static方法中的代码块加上synchronized也是一样,其实对static方法的同步可以理解为对类的class对象的同步。
对于某些基本类型如char,int,它们是原子性的,即使被共享访问,不需要考虑同步的问题,但是在某些jvm上,long和double是非原子性的,这时候需要对它们的访问同步化,除了synchronized方法块外,还可以直接用volatile修饰。

wait notify notifyAll
这三个方法在多线程开发中使用频率非常高,它们的实质都跟wait set有关。wait Set是一个执行该实例的wait方法时,所有停止的线程的集合,就像一个休息室。一旦发生如下几种动作:
1.有其他线程notify该线程。
2.有其他线程notifyAll。
3.有其他线程interrupt唤醒该线程。
4.wait方法到期。
线程都会退出wait Set。

interrupt、InterruptedException与sleep,wait,join的关系
线程的interrupt方法只是改变线程的中断状态,而sleep,wait,join在执行的时候会去检查线程的中断状态,如果是中断状态就抛出InterruptedException,否则该怎么着还是怎么着。因此并不是调用了interrupt线程就马上抛InterruptedException。还有个interrupted方法是设置线程的中断状态的。


多线程开发模式
1.共享互斥
  多个线程访问同1个资源,对调用这个资源的代码块加上synchronized。但是,如果这个共享资源是类中的某个字段,它的子类却不知道这个字段是需要同步的,就会引发继承异常,子类对这个字段的访问不同步就会有问题。synchronized是不能被子类继承的,子类若要同步需要显式的指定。同步比异步执行要消耗更多的性能,我自己的机器上测试是2-4倍甚至更高的时间消耗,因此同步的范围应该尽可能的小,也就是同步粒度应该尽可能的细化,一般的原则就是,synchronized代码块尽可能的小。

2.常量对象
  如果变量是只读性质的,可以让它常量化,这样就不需要互斥访问synchronized了,可以避免互斥带来的额外性能消耗。在只读共享对象被大量的线程并发访问的场景下,省下来的synchronized可以节省大量的性能。它一般要满足下面一些原则:
1.没有setter方法,因为这些方法会修改字段以及由字段引用的对象。
2.将字段声明为final和private访问类型。
3.不允许子类覆盖方法,最简单的办法是将类定义为final。稍微复杂点的方法是将构造函数定义为私有,并使用factory方法生成实例。
4.如果实例字段中包括可变对象的引用,则不允许这些对象被改变,也就是不要提供修该可变对象的方法。
5.不要将可变对象的引用共享;不要将可变对象的引用外部序列化;传递给构造函数的可变对象应复制备份赋给字段;不要返回原始对象,而应返回对象的clone。
Swing中大量使用了Immutable Object,比如Point、Rectangle等。由于用户界面属于输入响应交互式模式,因此存在大量的并发问题,尤其界面、数据一致性问题。Swing通过使用这些Immutable Object较好的避免了并发问题。
更灵活一点,可设计成2种类,一种可变,一种不可变,两者之间可以相互构造,就像StringBuffer和String。比如如下对象
public final class ImmutablePerson {
    private final String name;
    private final String address;
    public ImmutablePerson(String name, String address) {
        this.name = name;
        this.address = address;
    }
    public ImmutablePerson(MutablePerson person) {
        synchronized(person) {//去掉这个同步限制就会发现问题了
           this.name = person.getName();
           this.address = person.getAddress();
        }
    }
    public MutablePerson getMutablePerson() {
        return new MutablePerson(this);
    }
    public String getName() {
        return name;
    }
    public String getAddress() {
        return address;
    }
    public String toString() {
        return "[ ImmutablePerson: " + name + ", " + address + " ]";
    }
}
可以看到对象的字段全部是final的,而且没有set方法可更改值,跟它对比的有另外一个对象是可变的,提供了set方法但是有同步性能消耗。两者之间可相互构造。
public final class MutablePerson {
    private String name;
    private String address;
    public MutablePerson(String name, String address) {
        this.name = name;
        this.address = address;
    }
    public MutablePerson(ImmutablePerson person) {
        this.name = person.getName();
        this.address = person.getAddress();
    }
    public synchronized void setPerson(String newName, String newAddress) {
        name = newName;
        address = newAddress;
    }
    public synchronized ImmutablePerson getImmutablePerson() {
        return new ImmutablePerson(this);
    }
    String getName() {    // Called only by ImmutablePerson
        return name;
    }
    String getAddress() { // Called only by ImmutablePerson
        return address;
    }
    public synchronized String toString() {
        return "[ MutablePerson: " + name + ", " + address + " ]";
    }
}
然后通过main来测试
public class Main {
    public static void main(String[] args) {
        MutablePerson mutable = new MutablePerson("start", "start");
        new CrackerThread(mutable).start();
        new CrackerThread(mutable).start();
        new CrackerThread(mutable).start();
        for (int i = 0; true; i++) {
            mutable.setPerson("" + i, "" + i);
        }
    }
}

class CrackerThread extends Thread {
    private final MutablePerson mutable;
    public CrackerThread(MutablePerson mutable) {
        this.mutable = mutable;
    }
    public void run() {
        while (true) {
            ImmutablePerson immutable = new ImmutablePerson(mutable);
            if (!immutable.getName().equals(immutable.getAddress())) {
                System.out.println(currentThread().getName() + " ***** BROKEN ***** " + immutable);
            }
        }
    }
}
通过这个检测程序发现对象确实是不可变的,如果去掉注释,就会有问题了。

3.条件等待-唤醒/放弃
  这个模式大致归纳成3种必须元素:有循环,有条件检查,有等待唤醒或放弃。线程执行一个操作,发现条件不允许的时候,等待,直到条件满足后被唤醒;或者放弃当前操作,下次再执行。根据不满足条件的场景,一般有几类做法:条件不满足,当前线程无限等待,直到被其他线程唤醒;条件不满足,yield当前线程并继续下一次条件检查;条件不满足,sleep当前线程,再循环检查等。

4.生产消费模式
  如果需要线程A向线程B单向通信,而两者的处理速度差了很多,就可以使用这种方案。一般两者之间有一个缓冲队列或者缓冲区,生产者线程往队列里塞数据,消费者线程不断的从队列中拿数据执行。这样线程之间的共享访问冲突就集中在缓冲区上。下面是个简单的生产消费者例子。
用于缓冲数据的Box
public class Box {
    private final String[] buffer;
    private int tail;  // 下一个put的地方
    private int head;  // 下一个take的地方
    private int count; 
    public Box(int count) {
        this.buffer = new String[count];
        this.head = 0;
        this.tail = 0;
        this.count = 0;
    }
    public synchronized void put(String data) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " puts " + data);
        while (count >= buffer.length) {
            System.out.println(Thread.currentThread().getName() + " wait BEGIN");
            wait();
            System.out.println(Thread.currentThread().getName() + " wait END");
        }
        buffer[tail] = data;
        tail = (tail + 1) % buffer.length;
        count++;
        notifyAll();
    }
    public synchronized String take() throws InterruptedException {
        while (count <= 0) {
            System.out.println(Thread.currentThread().getName() + " wait BEGIN");
            wait();
            System.out.println(Thread.currentThread().getName() + " wait END");
        }
        String data = buffer[head];
        head = (head + 1) % buffer.length;
        count--;
        notifyAll();
        System.out.println(Thread.currentThread().getName() + " takes " + data);
        return data;
    }
}

生产者线程
public class ProducerThread extends Thread {
    private final Random random;
    private final Box box;
    private static int id = 0;
    public ProducerThread(String name, Box box, long seed) {
        super(name);
        this.box = box;
        this.random = new Random(seed);
    }
    public void run() {
        try {
            while (true) {
                Thread.sleep(random.nextInt(1000));
                String data = "[ Data No." + nextId() + " by " + getName() + " ]";
                box.put(data);
            }
        } catch (InterruptedException e) {
        }
    }
    private static synchronized int nextId() {
        return id++;
    }
}
消费者线程
public class ConsumerThread extends Thread {
    private final Random random;
    private final Box box;
    public ConsumerThread(String name, Box box, long seed) {
        super(name);
        this.box = box;
        this.random = new Random(seed);
    }
    public void run() {
        try {
            while (true) {
                String data = box.take();
                Thread.sleep(random.nextInt(1000));
            }
        } catch (InterruptedException e) {
        }
    }
}


5.读写锁
  它将读取和写入分开处理,对于读取并发不做共享互斥,对于读写、写写并发做互斥,适用于读取频繁,写入较少的共享互斥情况,比如读远多于写的缓存。这种情况下,比直接的共享互斥有很大的性能提高。它的主要核心就是读写锁,做读操作就获取读的锁,写操作就获取写的锁.下面是个很简单的读写锁实现:
public final class ReadWriteLock {
    private int readingReaders = 0; // 实际正在读取的线程数量
    private int waitingWriters = 0; // 正在等待写入的线程数量
    private int writingWriters = 0; // 实际正在写入的线程数量
    private boolean preferWriter = true; // 写入优先的话,值为true

    public synchronized void readLock() throws InterruptedException {
        while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) {
            wait();
        }
        readingReaders++;  
    }

    public synchronized void readUnlock() {
        readingReaders--;    
        preferWriter = true;
        notifyAll();
    }

    public synchronized void writeLock() throws InterruptedException {
        waitingWriters++; 
        try {
            while (readingReaders > 0 || writingWriters > 0) {
                wait();
            }
        } finally {
          waitingWriters--;
        }
        writingWriters++; 
    }

    public synchronized void writeUnlock() {
        writingWriters--;  
        preferWriter = false;
        notifyAll();
    }
}

然后是使用这个读写锁的共享数据
public class Data {
    private final char[] buffer;
    private final ReadWriteLock lock = new ReadWriteLock();
    public Data(int size) {
        this.buffer = new char[size];
        for (int i = 0; i < buffer.length; i++) {
            buffer[i] = '*';
        }
    }
    public char[] read() throws InterruptedException {
        lock.readLock();
        try {
            return doRead();
        } finally {
            lock.readUnlock();
        }
    }
    public void write(char c) throws InterruptedException {
        lock.writeLock();
        try {
            doWrite(c);
        } finally {
            lock.writeUnlock();
        }
    }
    private char[] doRead() {
        char[] newbuf = new char[buffer.length];
        for (int i = 0; i < buffer.length; i++) {
            newbuf[i] = buffer[i];
        }
        wating();
        return newbuf;
    }
    private void doWrite(char c) {
        for (int i = 0; i < buffer.length; i++) {
            buffer[i] = c;
            wating();
        }
    }
    private void wating() {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
        }
    }
}

读写线程就分别调用read和write就可。
public class ReaderThread extends Thread {
    private final Data data;
    public ReaderThread(Data data) {
        this.data = data;
    }
    public void run() {
        try {
            long begin = System.currentTimeMillis();
            for (int i = 0; i < 20; i++) {
                char[] readbuf = data.read();
                System.out.println(Thread.currentThread().getName() + " reads " + String.valueOf(readbuf));
            }
            long time = System.currentTimeMillis() - begin;
            System.out.println(Thread.currentThread().getName() + ": time = " + time);
        } catch (InterruptedException e) {
        }
    }
}
public class WriterThread extends Thread {
    private static final Random random = new Random();
    private final Data data;
    private final String filler;
    private int index = 0;
    public WriterThread(Data data) {
        this.data = data;
        this.filler = "hjshkjsfysfahfafaflkafsu7yyg";
    }
    public void run() {
        try {
            while (true) {
                char c = nextchar();
                data.write(c);
                Thread.sleep(random.nextInt(3000));
            }
        } catch (InterruptedException e) {
        }
    }
    private char nextchar() {
        char c = filler.charAt(index);
        index++;
        if (index >= filler.length()) {
            index = 0;
        }
        return c;
    }
}

在jdk1.5里有ReadWriteLock接口以及实现类ReentrantReadWriteLock。

分享到:
评论

相关推荐

    多线程单例模式并发访问

    总结起来,多线程环境下的单例模式实现需要注意线程安全问题,尤其是懒汉式单例,需要采取适当的同步措施来防止多线程环境下的实例化问题。此外,对于不同场景的需求,可以选择不同的实现方式来优化性能和资源使用。

    多线程基础总结.xmind

    多线程基础理论, 多线程中常用API,多线程的实现方式, 线程池以及创建线程池相关API, 常见的设计模式等内容

    JAVA多线程设计模式详解

    1. 单例模式:在多线程环境中保证单例对象的唯一性,常用双检锁/双重校验锁定(DCL)和静态内部类等方式实现。 2. 生产者-消费者模式:通过阻塞队列实现生产者线程和消费者线程之间的数据交换,利用BlockingQueue...

    java中常用设计模式总结心得

    1. **单例模式**:单例模式确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于配置管理、线程池或者数据库连接池等场景。实现方式有懒汉式(线程不安全)、饿汉式(静态常量)、双重检查锁定(DCL)以及...

    Java 多线程 设计模式

    以上只是Java多线程设计模式的一些基本概念和常用技术,实际开发中还需要根据具体需求灵活运用,结合设计模式来解决问题。《Java 多线程设计模式》一书的源代码应该涵盖了这些知识点的具体实现,通过阅读和实践,...

    Java多线程-多线程知识点总结和企业真题

    ### Java多线程知识点总结及企业真题解析 #### 一、知识点总结 ##### (1)多线程相关概念 1. **程序、进程和线程的区分**: - **程序**:为了完成特定的任务而编写的指令集合。它是静态的概念。 - **进程**:...

    java多线程设计模式详解PDF及源码

    设计模式则是解决软件开发中常见问题的经验总结,它为多线程环境下的编程提供了有效的解决方案。本资源包括了详细的“Java多线程设计模式详解”PDF文档以及配套的源码,帮助开发者深入理解和应用这些模式。 首先,...

    多线程服务器的几种常用模型

    ### 多线程服务器的几种常用模型 #### 1. 进程与线程 在计算机科学中,**进程**和**线程**是两个重要的概念。进程是资源分配的基本单位,而线程则是调度的基本单位。每个进程都有自己的独立地址空间,这意味着不同...

    多核多线程下java设计模式性能提升.pdf

    总结来说,本文档通过介绍单例模式在多核多线程环境下的性能提升方法,探讨了Java设计模式在现代大型系统应用中的优化策略,并对JDK未来的设计模式实现提出期望,为Java开发人员提供了宝贵的技术指导和参考。

    Java多线程编程环境中单例模式的实现

    ### Java多线程编程环境中单例模式的实现 #### 概述 单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Java中,单例模式的应用非常广泛,特别是在资源管理、日志记录、...

    Java 多线程学习总结6

    在“Java多线程学习总结6”这个主题中,我们可以深入探讨Java多线程的实现、管理及优化。下面将详细阐述相关知识点。 1. **线程的创建方式** - **继承Thread类**:自定义类继承Thread类,并重写run()方法,创建...

    多线程编程指南

    提供了一个包含多线程编程中常用术语的词汇表,方便读者查阅。 #### 结束语 本指南详细介绍了多线程编程在iOS开发中的应用,希望读者能够从中受益,并在实际项目中有效地运用多线程技术。 #### 推荐资源 最后,...

    ios 多线程编程指南

    对于一些常用的框架,如Foundation Framework、ApplicationKit和CoreData,Apple提供了线程安全的总结,指导开发者如何在这些框架内安全地使用多线程。 以上就是iOS多线程编程指南的主要知识点,详细解读了多线程...

    java 多线程单例模式详解

    ### Java多线程单例模式详解 #### 一、单例模式概述 单例模式(Singleton Pattern)是一种常用的软件设计模式,它确保一个类仅有一个实例,并提供一个全局访问点。这种模式通常用于那些需要频繁实例化然后销毁的...

    多线程,多接收模式串口类LsComm

    1. **多线程支持**:LsComm类库的一大亮点在于其内置的多线程机制。在串口通信中,多线程可以同时处理多个接收任务,避免了单线程模型下的阻塞问题,提高了系统的并发性能。例如,一个线程负责读取数据,另一个线程...

    几种常用的设计模式介绍总结

    单例模式是一种常用的创建型设计模式,它保证一个类仅有一个实例,并提供一个全局访问点。单例模式通常用于控制资源的共享访问,例如数据库连接池、日志管理器等。 #### 实现方式 - **构造函数私有化**:防止外部...

    支持多线程AC自动机

    总结来说,支持多线程AC自动机的Snort改进版是一种高效的方法,它结合了AC自动机的快速搜索能力和多线程的并发处理能力,旨在提高网络入侵检测系统的性能和响应速度,对于应对现代网络安全挑战具有重要意义。

    Java多线程技术

    在多线程编程中,常用的设计模式有生产者-消费者模式、读写锁模式等。 10. 多线程工具类:Java提供了诸如Timer、ScheduledExecutorService等工具类,它们能够帮助开发者更方便地进行时间安排、任务调度等操作。 ...

    使用C++11实现线程安全的单例模式

    在C++编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。线程安全的单例模式在多线程环境下尤其重要,因为不正确的实现可能导致多个线程创建多个实例,这违反了单例模式...

Global site tag (gtag.js) - Google Analytics