Java多线程模式-immutable模式

场景

immutable的意思是“不可变”

设计一个类,实例的内部状态不会发生变更,不用使用锁机制,线程安全。


代码实现

下面我设计一个Person的类,表示人

<code>public final class Person {

   private final String sex;
   private final String id;
   private final String name;

   public Person(String sex, String id, String name) {
       this.name = name;
       this.id = id;
       this.sex = sex;
  }
   public String getName() {
       return name;
  }

   public String getSex() {
       return sex;
  }

   public String getId() {
       return id;
  }

   @Override
   public String toString() {
       return "Person{" +
               "sex='" + sex + '\\'' +
               ", id='" + id + '\\'' +
               ", name='" + name + '\\'' +
               '}';
  }
}/<code>

说明:

1、属性只能在构造函数中赋值,没有setter方法,只有getter方法;

2、字段都是private final,意味着只能赋值一次,且只能在内部访问

3、类是final,表示无法创建子类,防止子类修改


下面我们来测试一下该类是否是线程安全的

实现显示Person信息的类

<code>
public class ShowPersonThread extends Thread {

   private Person person;

   public ShowPersonThread(Person person) {
       this.person = person;
  }

   public void run() {
       while (true) {
           System.out.println(Thread.currentThread().getName() + "——" + person);
      }
  }
}/<code>

实现主函数类

<code>
public class Main {
   public static void main(String[] args) {
       Person alice = new Person("女", "1222323", "lucy");
       new ShowPersonThread(alice).start();
       new ShowPersonThread(alice).start();

       new ShowPersonThread(alice).start();
  }
}/<code>


模式解读

什么时候使用?

1、实例在创建后,状态不会发生变化

比如上面的Person类,一旦创建,字段不会发生变化,不发生变化需要结合具体业务涉及,对于人而言,sex/id不会发生变化,一出生就决定了,name发生变化得几率很小

注意引用字段,引用字段的值不会变,但是其指向的值发生了变化

2、实例被共享

多个线程同时访问

由于没有使用锁,因此性能会比较高,对于那种需要频繁访问且很少变化的数据,能提高性能


String & StringBuffer

String

<code>
private final char value[];/<code>

注意字段是private final,无法变更

StringBuffer

<code>
AbstractStringBuilder
char[] value; /<code>

String是线程安全的,StringBuffer是线程不安全


final

1、修饰类,表示无法创建子类,子类无法重写其方法

2、修饰非静态方法,表示无法被子类重写;修饰静态方法,表示不会被子类隐藏

3、修饰字段

表示只能赋值一次

初始化方法:

比如:private final age= 1;

(2)构造函数中赋初值

对于静态final字段,初始化方法:

比如:private static final age= 2;

(2)静态代码块赋初值

private static final age;

static {

​ age = 12;

}

4、局部变量和函数参数

局部变量只能赋初值一次

函数参数不能再赋值,因为在调用的时候已经赋值一次,不能再赋值


ArrayList

ArrayList是可变大小的“数组“,是非线程安全的,下面用代码证实这一点

写线程类

<code>
import java.util.List;

public class WriteThread extends Thread{

   private final List<string> list;

   public WriteThread(List<string> list) {
       super("写线程");
       this.list = list;
  }

   public void run() {

       int i = 0;
       while(true) {
           list.add(String.valueOf(i));
           list.remove(0);
           i++;
      }
  }
}/<string>/<string>/<code>

读线程类

<code>
import java.util.List;

public class ReadThread extends Thread{

   private final List<string> list;

   public ReadThread(List<string> list) {
       super("读线程");
       this.list = list;
  }

   public void run() {
       while (true) {
           for (String str : list) {
               System.out.println(str);
          }
      }
  }
}/<string>/<string>/<code>

主函数类

<code>
import java.util.ArrayList;
import java.util.List;

public class Main {
   public static void main(String[] args) {
       List<string> list = new ArrayList<string>();
       new WriteThread(list).start();
       new ReadThread(list).start();
  }
}/<string>/<string>/<code>

执行程序,结果如下:

<code>
Exception in thread "ReaderThread" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.vedda.immutable.collection.sample1.ReadThread.run(ReadThread.java:16)/<code>


解决线程安全方法

使用Collections.synchronizedList

修改主函数如下:

<code>
public class Main {
   public static void main(String[] args) {
       final List<integer> list = Collections.synchronizedList(new ArrayList<integer>());
       new WriterThread(list).start();
       new ReaderThread(list).start();
  }
}/<integer>/<integer>/<code>

读线程类run 方法修改如下:

<code>
public void run() {
       while (true) {
           synchronized (list) {
               for (String str : list) {
                   System.out.println(str);
              }
          }
      }
  }/<code>

输出结果如下:

<code>2
1891942
1891942/<code>

运行正常,但是输出结果不是连续的,因为在读的时候,可以写线程已经写入多个值了


2、使用CopyOnWriteArrayList

修改主函数如下:

<code>public class Main {
   public static void main(String[] args) {
       final List<integer> list = new CopyOnWriteArrayList<integer>();
       new WriterThread(list).start();
       new ReaderThread(list).start();
  }
}/<integer>/<integer>/<code>

修改读线程run方法:

<code>public void run() {
       while (true) {
           for (String str : list) {
               System.out.println(str);
          }
      }
  }/<code>

正常运行,输出结果如下:

<code>0
16
18
18/<code>

CopyOnWriteArrayList每次写操作时,都会复制集合,因此它比较适合读多写少场景。


分享到:


相關文章: