Java线程同步的实现

来源:互联网 发布:国家顶级域名证书 编辑:程序博客网 时间:2024/05/08 05:38

线程共享受限资源

1、基本上所有的并发模式在解决线程冲突的时候,都是采用序列化访问共享资源的方案,这一位着在给定时刻只允许一个任务访问共享资源;
这通常是在代码前加上一条锁语句实现的,这种机制常常被称为互斥量(mutex)


2、隐式加锁同步使用synchronized关键字

1)共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入/输出端口、打印机;
2)要控制共享资源的访问,首先要将其包装进一个对象然后要将所有访问该资源的方法(或代码块)标记为synchronized
     同时要将该共享域标记为private
3)示例代码
1、对整个方法进行同步class demo{     private int shareResource;     public synchronized void method1(){  shareResource ++;  }     public synchronized void method2(){  shareResource --;  }}2、对方法中需要同步的地方进行同步class demo{     private int shareResource;     public void method1(){          System.out.println("method1 start");          synchronized(this){               shareResource ++;            }           System.out.println("method1 end");      }     public void method2(){          System.out.println("method2 start");          synchronized(this){               shareResource --;           }          System.out.println("method2 end");     }}

4)对于某个特定的对象来说,其范围内所有的synchronized方法共享一个锁;
5)每个访问临界共享资源的方法都必须被同步,否则其他方法会随意忽视该锁;


3、显式加锁同步:利用Lock进行加锁

1)java SE5中的java.util.concurrent.Locks 可以显式地定义互斥机制;
2)示例代码
class Demo{     private int shareResource;     private Lock lock = new ReentrantLock();     public int method{          lock.lock();          try{               shareResource ++;               return shareResource;          }finallly{               lock.unlock();          }     }//将同步块放置在try-finally块中,return必须在try块中,以确保unlock不会过早发生,从而将数据暴露给第二个任务;}


3)使用synchronized隐式加锁的代码量更少,通常在一些特殊情况下才会使用Lock对象,如:
①使用synchronized不能尝试获取锁,且最终获取锁失败时只会抛出一个异常,无法进行任何的清除工作;
②尝试获取锁一段时间,然后释放它;
class Demo{     private Reentrantock lock = new ReentrantLock();     public void method1(){          boolean result = lock.tryLock();   //仅在调用时,lock处于空闲状态才获取锁;          try{               System.out.println(result);          }finally{               if(result)                    lock.unlock();          }     }     public void method2(){          boolean result = false;          try{               result = lock.tryLock(2,TimeUnit.SECONDS);                 //在调用时,lock在2seconds内处于空闲状态,且线程在这个时间段类没有被打断,才获取锁;          }catch(InterruptedException e){               System.out.println(result);          }finally{               if(result)                    lock.unlock();          }     }}





4、通过限制1个许可信号量来模拟一个互斥的锁(信号量时用来限制访问共享资源的线程数)

  class Task{     Semaphore semaphore = new Semaphore(1);     public void xMethod(){           try{                 semaphore.acquire();                 statement;           }catch(InterruptedException ex){           }finally{                 semaphore.release();           }     }  }




5※、利用原子类取代synchronized互斥同步

1)原子性原子操作不需要进行同步控制,原子操作时不能被线程调度中断的操作,一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前执行完毕,可以利用这一点的特定来编写无锁的代码;
2)原子性可以应用在除了long和double之外的所有基本类型之上的“简单操作”,对于读写除了long和double之外的基本变量这样的操作,可以保证他们会被当做不可分割的操作来操作内存;

3)原子类:Java SE5引入了如AtomicInteger,AtomincLong,AtomicReference等特殊的原子性变量,它们提供以下形式的原子性条件更新操作:
这些类被调整为使用在某些现代处理器上可获得的,并且在机器级别上的原子性,一般应用在性能调优上;
boolean compareAndSet(expectedValue,updateValue); 当前值==预期值,则以原子方式将该值设置为给定的值
int getAndAdd(int delta);  以原子的形式将给定值与当前值相加
int getAndSet(int delta);  以原子形式将当前值设置为给定值
int get();
void set(int newValue);
如:AtomicInteger i = new AtomicInteger(1024);
    i.compareAndSet(1024,2048);   //该操作是线程安全的
4)示例代码
import java.util.concurrent.atomic.*;class Demo{     private AtomicInteger count = new AtomicInteger(0);     public int getValue(){  return count.get(); }     public void method1(){ count.getAndAdd(20);}     public void method2(){ count.getAndDerement(); //count++}     public void method3(){ count.getAndIncrement(); //count--}      //Test     public static void main(){          Demo demo = new Deme();          ExecutorService exec = Executors.newCachedThreadPool();          exec.execute(new Runnable(){               public void run(){                    while(true){                         demo.method1();                         System.out.println(demo.getValue());                    }               }          });           exec.execute(new Runnable(){               public void run(){                    while(true){                         demo.method2();                         System.out.println(demo.getValue());                    }               }          });           exec.execute(new Runnable(){               public void run(){                    while(true){                         demo.method3();                         System.out..println(demo.getValue());                    }               }          });     }}//对数据进行原子性操作,不用使用synchronized就可以对操作进行同步;




6、临界区
1)临界区(critical section)/同步控制块:有时候只是希望多个线程同时访问方法内部的部分代码,而不是整个方法,这种方式分离出来的代码块就是临界区;
synchronized(syncObject){
     statements;
}
2)在进入该同步控制块之前,必须获取syncObject对象的锁如果其他线程已经获取该锁,那么要等到该锁释放后,才能进入该临界区;
3)使用同步控制块取代对整个方法进行同步控制,可以使多个任务访问对象的时间性能得到显著地提升;
4)synchronized块必须给定一个在其上进行同步的对象(一般比较合理的使用时synchronized(this)即本对象);
     有时必须在另一个对象上同步,此时必须保证所有相关的任务都是在同一个对象上同步的,此时两个同步控制块是相互独立的,他们不会因为对方而阻塞,示例:
//解决限制:一个对象只能获取一个同步锁————使得两个任务可以同时进入同一个对象class Demo{     private Object syncObject = new Object();     public synchronized void method1(){          while(true){               println("method1()");               Thread.yield();          }     }     public void method2(){          synchronized(syncObject){               while(true){                    println("method2()");                    Thread.yield();               }          }     }    //Test     public static void main(){          final Demo demo = new Demo();          new Thread(){               public void run(){                    demo.method1();               }          }          demo.method2();     }}




7、线程本地储存ThreadLocal
1)除了以上使用互斥量同步的方法外,放置任务子在共享资源上产生冲突的第二种反方式:根除对变量的共享;
2)在java中的线程本地储存是一种自动化机制可以为使用相同变量的每一个线程创建不同的储存,可以使用 java.lang.ThreadLocal 来实现;
3)实例代码
class Demo{     private static ThreadLocal<Integer> value = new ThreadLocal<Integer>();     value.set(0);     public static void mian(String[] args){          ExecutorService executor = Executors.newCachedThreadPool();          for(int i=0;i<5;i++){               executor.execute(new Runnable(){                    public void run(){                              value.set(value.get()+i);                         System.out.print(value.get()+",");                    }               });          }          TimeUnit.SECONDS.sleep(3);          executor.shutdown();     }}/*output:     0,1,2,3,4*///只能使用set(),get()来修改和访问TreadLocal的数据;






0 0
原创粉丝点击