并发(二)

本篇讲述一下多线程中,共享受限资源。

本文只是自己学习过程中的总结,知识点整理,会引用书中或是他人大量的代码或是观点。我会尽量标记出处。

 参考文章:

http://blog.csdn.net/luoweifu/article/details/46613015

《Java编程思想》     参考了许多代码和观点,在此声明。

1.不正确的访问资源

请看例子:

 1 public class ThreadDemo02 implements Runnable{
 2 
 3     private static int i = 0;
 4     
 5     public void run() {
 6         //synchronized (this) {
 7             System.out.println(Thread.currentThread().getName() + " i value is " + i++);
 8         //}
 9     }
10 }
View Code
 1 import java.util.concurrent.ExecutorService;
 2 import java.util.concurrent.Executors;
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class ThreadDemo03 {
 6 
 7     public static void main(String[] args) throws InterruptedException {
 8 
 9         ThreadDemo02 demo02 = new  ThreadDemo02();
10         ExecutorService service = Executors.newCachedThreadPool();
11         for (int i = 0; i < 10; i++) {
12             service.execute(demo02);
13         }
14         service.shutdown();
15     }
16 }
View Code

这是实际执行的结果:

 1 pool-1-thread-2 i value is 0
 2 pool-1-thread-6 i value is 3
 3 pool-1-thread-3 i value is 1
 4 pool-1-thread-4 i value is 2
 5 pool-1-thread-1 i value is 0
 6 pool-1-thread-5 i value is 5
 7 pool-1-thread-7 i value is 4
 8 pool-1-thread-8 i value is 6
 9 pool-1-thread-3 i value is 7
10 pool-1-thread-8 i value is 8
View Code

而我期待的结果是下面这样的:

 1 pool-1-thread-1 i value is 0
 2 pool-1-thread-6 i value is 1
 3 pool-1-thread-5 i value is 2
 4 pool-1-thread-7 i value is 3
 5 pool-1-thread-4 i value is 4
 6 pool-1-thread-3 i value is 5
 7 pool-1-thread-2 i value is 6
 8 pool-1-thread-8 i value is 7
 9 pool-1-thread-5 i value is 8
10 pool-1-thread-8 i value is 9
View Code

这是因为共享资源竞争造成的结果。

 

2.解决共享资源竞争

 2.1第一种方式是使用synchronized的方式。

在这里需要注意的是,synchronized锁的对象指的是同一对象内的。如果有两个对象,那么就会有两把锁,两把锁之间相互独立,没有影响。

在使用并发时,将域设置为private是非常重要的,否则,synchronized关键字就不能防止其他任务直接访问域,这样就会产生冲突。

syschronized是Java中的关键字,它修饰的对象有以下几种:

(1)修饰域,被称为同步代码块,作用的对象是调用这个方法的对象;

(2)修饰方法,作用的范围是整个方法,作用的对象是调用这个方法的对象;

(3)修饰静态方法,作用的对象是这个类的所有对象;

(4)修饰类,作用的对象是整个类的所有的对象。

 

 在用synchronized修饰方法时要注意以下几点:

(1)synchronized关键字不能被继承。虽然,可以使用synchronized来定义方法,但是synchronized并不属于方法定义的一部分,因此它不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,那么子类的这个方法默认情况下并不是同步的,而必须显示第在子类的这个方法中加上synchronized关键字。当然,还可以在子类中调用父类的方法,虽然子类中的方法不是同步的,但是子类调用了父类的同步方法,因此,子类的方法也就相当于是同步了。

(2)构造方法不能使用synchronized关键字,但是在方法内部可以使用synchronized代码块。

(3)下面使用synchronized的方式是等价的。

1 public synchronized void method {
2     // to do
3 }
4 
5 public void method {
6     synchronized(this) {
7         
8     }
9 }
View Code

 

2.2使用显示的Lock对象

Lock对象必须被显式第创建、锁定、和释放,跟内建的锁形式相比,代码缺乏优雅性,但是更加灵活,赋予了你更加细粒度的控制力。这对于实现专用结构是很有用的,例如用于遍历链表中的节点的节节传递的加锁机制,这种遍历代码必须在释放当前节点的锁之前捕获下一个节点的锁。(红色部分观点摘自《Java 编程思想》)

这里我将JDBC的transaction联系起来,觉得这样大家会更好理解。

自己去设置上锁和解锁的时间点,跟事务开始结束的设置,我认为是一样的。

 lock的代码如下:

 1 import java.util.concurrent.locks.Lock;
 2 import java.util.concurrent.locks.ReentrantLock;
 3 
 4 public class ThreadDemo02_lock implements Runnable{
 5 
 6     private static int i = 0;
 7     private Lock lock = new ReentrantLock();
 8     public void run() {
 9         try {
10             lock.lock();
11             System.out.println(Thread.currentThread().getName() + " i value is " + i++);
12         } finally {
13             lock.unlock();
14         }
15     }
16 }
View Code

 

2.3原子性和易变性

原子性可应用于除long和double之外的所有基本类型之上的简单操作。但是当你定义long或double变量时,如果使用volatile关键字时,就会获得原子性。

volatile关键字确保了应用中的可见性。如果将一个域声明为volatile,那么是要对这个域产生了写操作,那么所有的读操作就都可以看到这个修改。

如果多个任务在同时访问某个域,那么这个域就应该是volatile的,否则,这个域就应该只能由同步来访问。

记住第一选择是synchronized,这是最安全的,而尝试其他任何方式都是有风险的。

在java中, i++等操作不是原子性的。

下面看一个例子:

 1 public class ThreadDemo04 implements Runnable{
 2 
 3     private int i=0;
 4     
 5     // 为了保证获取I的数值时,能达到稳定状态,应该同步地使用getValue,也就是加上synchronized关键字
 6     public int getValue() {
 7         // 虽然return i是原子性操作,但是缺少同步使得其数值可以在处于不稳定的中间状态时被读取。
 8         return i;
 9     }
10     
11     @Override
12     public void run() {
13         while (true) {
14             evenIncrement();
15         }
16     }
17 
18     private synchronized void evenIncrement() {
19         i++;
20         i++;
21     }
22 
23     public static void main(String[] args) {
24         ExecutorService service = Executors.newCachedThreadPool();
25         ThreadDemo04 demo04 = new ThreadDemo04();
26         service.execute(demo04);
27         while (true) {
28             int val = demo04.getValue();
29             if(val%2 != 0) {
30                 System.out.println(val);
31                 System.exit(0);
32             }
33         }
34     }
35 }
View Code

 

2.4临界区

 也称为同步控制块。进入代码之前,必须得到对象的锁,如果其他线程已经得到这个锁,那么就得等到锁被释放以后,才能进入临界区。

 1 public class Pair {
 2 
 3     private int x, y;
 4 
 5     public Pair(int x, int y) {
 6         this.x = x;
 7         this.y = y;
 8     }
 9 
10     public Pair() {
11         this(0, 0);
12     }
13 
14     public int getX() {
15         return x;
16     }
17 
18     public int getY() {
19         return y;
20     }
21 
22     public void incrementX() {
23         x++;
24     }
25 
26     public void incrementY() {
27         y++;
28     }
29 
30     public String toString() {
31         return "x: " + x + ", y: " + y;
32     }
33     
34     public class PairValuesNotEquationException extends RuntimeException {
35 
36         private static final long serialVersionUID = 1L;
37         
38         public PairValuesNotEquationException(){
39             super("Pair values not equal: " + Pair.this);
40         }
41     }
42     
43     public void checkState() {
44         if (x != y) {
45             throw new PairValuesNotEquationException();
46         }
47     }
48 }
View Code
 1 import java.util.ArrayList;
 2 import java.util.Collections;
 3 import java.util.List;
 4 import java.util.concurrent.TimeUnit;
 5 import java.util.concurrent.atomic.AtomicInteger;
 6 
 7 public abstract class PairManager {
 8 
 9     AtomicInteger checkCounter = new AtomicInteger(0);
10     protected Pair p = new Pair();
11     private List<Pair> storange = Collections.synchronizedList(new ArrayList<Pair>());
12     public synchronized Pair getPair() {
13         return new Pair(p.getX(), p.getY());
14     }
15     protected void store(Pair p){
16         storange.add(p);
17         try {
18             TimeUnit.MICROSECONDS.sleep(50);
19         } catch (InterruptedException ignore){
20             
21         }
22     }
23     
24     public abstract void increment();
25 }
View Code
 1 public class PairManager1 extends PairManager {
 2 
 3     @Override
 4     public synchronized void increment() {
 5         p.incrementX();
 6         p.incrementY();
 7         store(getPair());
 8     }
 9 
10 }
View Code
 1 public class PairManager2 extends PairManager {
 2 
 3     @Override
 4     public void increment() {
 5 
 6         Pair pair;
 7         synchronized (this) {
 8             p.incrementX();
 9             p.incrementY();
10             pair = getPair();
11         }
12         store(pair);
13     }
14 
15 }
View Code
 1 public class PairManipulator implements Runnable {
 2 
 3     private PairManager pm;
 4     public PairManipulator(PairManager pm) {
 5         this.pm = pm;
 6     }
 7     @Override
 8     public void run() {
 9 
10         while (true) {
11             pm.increment();
12         }
13     }
14     
15     public String toString () {
16         return "Pair: " + pm.getPair() + " checkCounter = " + pm.checkCounter.get();
17     }
18 
19 }
View Code
 1 import java.util.concurrent.ExecutorService;
 2 import java.util.concurrent.Executors;
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class CriticalSection {
 6 
 7     static void testApproaches(PairManager pman1 , PairManager pman2) {
 8         ExecutorService exec = Executors.newCachedThreadPool();
 9         PairManipulator pm1 = new PairManipulator(pman1);
10         PairManipulator pm2 = new PairManipulator(pman2);
11         PairChecker pch1 = new PairChecker(pman1);
12         PairChecker pch12= new PairChecker(pman2);
13         exec.execute(pm1);
14         exec.execute(pm2);
15         exec.execute(pch1);
16         exec.execute(pch12);
17         try {
18             TimeUnit.MICROSECONDS.sleep(500);
19         } catch (InterruptedException ex) {
20             System.out.println("sleep intertuped");
21         }
22         System.out.println("pm1: " + pm1 + "\npm2: " + pm2);
23         System.exit(0);
24     }
25     
26     public static void main(String[] args){
27         PairManager pm1 = new PairManager1();
28         PairManager pm2 = new PairManager2();
29         testApproaches(pm1, pm2);
30     }
31 }
View Code

上面这个完成的例子,执行完后,就可以看到结果,从结果中可以看到同步控制块的效率要远高于同步方法。此外,也展示了如何把一个非保护类型的类,在其他类的保护和控制之下,应用于多线程的环境。

 

2.5在其他对象上同步

下面的示例展示了两个任务可以同时进入同一个对象,只要这个对象的方法是在不同的锁上同步的即可。

 1 public class DualSynch {
 2 
 3     private Object object = new Object();
 4     public synchronized void f() {
 5         for (int i = 0; i < 5; i++) {
 6             System.out.println("f()");
 7             Thread.yield();
 8         }
 9     }
10     
11     public void g() {
12         synchronized (object) {
13             for (int i = 0; i < 5; i++) {
14                 System.out.println("g()");
15                 Thread.yield();
16             }
17         }
18     }
19 }
View Code
 1 public class SyncObject {
 2 
 3     public static void main(String[] args) {
 4 
 5         final DualSynch dualSynch = new DualSynch();
 6 
 7         new Thread(()-> dualSynch.f()).start();
 8 /*        new Thread() {
 9             public void run() {
10                 dualSynch.f();
11             }
12         }.start();*/
13         dualSynch.g();
14     }
15 }
View Code

 

2.6线程本地存储

线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。

例子如下:

 1 public class Accessor implements Runnable {
 2 
 3     private final int id;
 4     public Accessor (int idn) {
 5         id = idn;
 6     }
 7     @Override
 8     public void run() {
 9         while (!Thread.currentThread().isInterrupted()) {
10             ThreadLocalVariableHolder.increment();
11             System.out.println(this);
12             Thread.yield();
13             }
14     }
15 
16     public String toString() {
17         return "#" + id + ": " + ThreadLocalVariableHolder.get();
18     }
19 }
View Code
 1 import java.util.Random;
 2 import java.util.concurrent.ExecutorService;
 3 import java.util.concurrent.Executors;
 4 import java.util.concurrent.TimeUnit;
 5 
 6 public class ThreadLocalVariableHolder {
 7 
 8     private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
 9         private Random rand = new Random(47);
10 
11         protected synchronized Integer initialValue() {
12             return rand.nextInt(100);
13         }
14     };
15 
16     public static void increment() {
17         value.set(value.get() + 1);
18     }
19 
20     public static int get() {
21         return value.get();
22     }
23     public static void main(String[] args) throws InterruptedException {
24 
25         ExecutorService service = Executors.newCachedThreadPool();
26         for (int i = 0; i < 4; i++) {
27 
28             service.execute(new Accessor(i));
29         }
30         TimeUnit.SECONDS.sleep(1);
31         service.shutdownNow();
32     }
33 
34 }
View Code

 

写到这里,这部分算是完事了,之而的文章将进入下一小节的学习了。

 

posted @ 2017-09-30 13:52  Mr.袋鼠  阅读(207)  评论(0编辑  收藏  举报