zoukankan      html  css  js  c++  java
  • 并发(二)

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

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

     参考文章:

    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 + "
    pm2: " + 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

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

  • 相关阅读:
    关于VS2008单元测试中加载配置文件的问题
    面向对象控与python内存泄漏
    热酷,新的领域,新的发展
    [思想火花]:函数命名及参数
    使用AuthToken架构保护用户帐号验证Cookie的安全性
    竟然遇到取FormAuthentication的值取不出来的情况
    新头衔:热酷高级python软件工程师,你问我去热酷干嘛?
    浅谈滚服游戏如果实现一键合服
    基础
    简介
  • 原文地址:https://www.cnblogs.com/lihao007/p/7614505.html
Copyright © 2011-2022 走看看