zoukankan      html  css  js  c++  java
  • Java多线程 线程同步

    如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你需要使用同步,并且,读写线程都必须用相同的监视器锁同步。--Brain同步规则

    synchronized

    1. 所有对象都自动含有单一的锁,当在调用一个对象的任意synchronized方法时,此对象将被加锁。
    2. 对于某个特定对象来说,所有的synchronized方法共享同一个锁。所以某个线程在访问对象的一个synchronized方法时,其他线程访问该对象的任何synchronized方法都将被阻塞。
    3. 一个任务可以多次获得对象锁,如在调用对象的一个方法时,该方法又调用了其他的方法。JVM负责跟踪对象被加锁的次数,这也是锁的可重入性,不可重入的锁可能造成死锁。
    4. 每个类也有一个锁,每个类都是一个class对象,synchronized static方法可以防止对static数据的并发访问
    5. 在使用并发时,最好将字段设置为private,防止线程直接访问字段。

    可重入锁ReentrantLock

    在java.util.concurrent.locks包中定义了显式的Lock,该Lock锁需要显式的创建、锁定和释放。比起synchronized关键字,显式的Lock锁使用起来更繁琐,在使用时也更有可能出错,但有更强大的功能。locks包中包含了两种锁ReentrantLock和ReentrantReadWriteLock。

    1. ReentrantReadWriteLock,看了些博文了解了下。线程进入读锁的前提条件,(a)没有其他线程的写锁,(b)没有写请求或者有写请求,但调用线程和持有锁的线程是同一个;线程进入写锁的前提条件,(a)没有其他线程的读锁,(b)没有其他线程的写锁。
    2. ReentrantLock有两种构造,ReentrantLock(boolean fair) fair为true告诉创建的锁为公平锁,不传fair参数创建非公平锁。非公平锁是直接获取锁,没有维护等待队列;公平锁依然需要检查当前线程是否是等待队列的第一个。
    public class LockTest {
        private int current = 0;
        private Lock lock = new ReentrantLock();
        
        public int next(){
            //lock.lock();
            boolean flag = lock.tryLock();
            if(flag){
                try{
                    ++current;
                    Thread.yield();
                    ++current;
                    return current;
                }finally{
                    lock.unlock();
                }
            }else{
                System.out.println("locked");
                return -1;
            }
        }
    }

    显示锁可以去尝试获取锁,这是比synchronized关键字更为灵活的地方。tryLock(long timeout, TimeUnit unit)还可以设置你等待获取锁的时间。由于Lock锁更有可能出错,除非解决特殊问题,否则使用synchronized关键字即可。

    volatile

    使用volatile关键字可以使你定义的基本类型变量的赋值和返回操作为原子操作。

    volatile可以是声明的字段具有可视性。可视性指只要你对字段进行了写操作,那么所有的读操作就都可以看到这个修改。因为线程对某一字段的修改,可能不会直接体现在内存中,其他线程也就获取不到最新的值。

    volatile关键字应该慎重使用,需要进行线程同步时,首选还是synchronized关键字。

    原子类

    Java SE5提供了AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类。这些类以boolean compareAndSet(int expect, int update)的形式进行原子性条件的更新。这些原子类都有基本的get、set操作,用来获取和设置值。

    Atomic类的设计主要用来构建java.util.concurrent中的类,常规编程还是使用锁要更安全些,但是在涉及性能调优时,这些原子类就大有用武之地了。

    public class AtomicityTest implements Runnable {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            new Timer().schedule(new TimerTask() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.err.println("aborting");
                    System.exit(0);
                }
            }, 5000);
            ExecutorService exec = Executors.newCachedThreadPool();
            AtomicityTest at = new AtomicityTest();
            exec.execute(at);
            
            while(true){
                int val = at.getValue();
                if(val % 2 != 0){
                    System.out.println(val);
                    System.exit(0);
                }
            }
        }
        
        private AtomicInteger i = new AtomicInteger(0);
        public int getValue(){
            return i.get();
        }
        
        private void evenIncrement(){
            i.addAndGet(2);
            System.out.println(i);
        }
        
        @Override
        public void run(){
            while(true)
                evenIncrement();
        }
    }

    临界区和其他对象上同步

    有时我们只希望某部分代码不被多个线程同时访问,这类被同步的代码块叫临界区。临界区可以使用synchronized关键字来建立。

    synchronized(syncObject) {   //临界区代码   }

    某个线程在进入临界区代码块时,必须获得syncObject对象上的锁。需要注意的是,syncObject既可以是对象本身this,也可以使用其他的对象。在介绍synchronized关键字时说过,所有对象都含有一个锁,临界区就是通过对象锁来实现的。这样多个线程可以同时进入同一个对象,只要该对象被访问的代码块使用了不同的对象锁。

    ThreadLocal

    ThreadLocal通过根除对变量的共享来防止多线程中共享资源的冲突。ThreadLocal为使用相同的变量的每个不同的线程都创建不同存储。ThreadLocal对象通常当做静态字段来存储。下面的代码,相当于每一个线程都为其设置了一个Integer id。

    public class ThreadLocalTest {
        public static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
            protected synchronized Integer initialValue() {
                return 100;
            }
        };
        
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            new Timer().schedule(new TimerTask() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.exit(0);
                }
            }, 5000);
            
            ExecutorService exec = Executors.newCachedThreadPool();
            for(int i=0; i<10; ++i){
                exec.execute(new Accesor(value.get()));
            }
            exec.shutdown();
        }
    
    }
    class Accesor implements Runnable {
        private int id;
        
        public Accesor(Integer id){
            this.id = id.intValue();
            System.out.println("init value: " + this.id);
        }
        
        public void run(){
            while(true) id++;
        }
    }
  • 相关阅读:
    大学总结(一)
    关于数组名与指针的相互转换
    错误:无法执行操作,因为未将指定的 Storyboard 应用到此交互控件的对象
    延迟初始化 (Lazy Initialization)
    Sql Server 中 GAM、SGAM、PAM、IAM、DCM 和 BCM 的详解与区别
    Xml格式的字符串(string)到DataSet(DataTable)的转换
    Sql Server 内存用不上的解决办法
    Sql Server 管理区分配(GAM,SGAM)和可用空间(PAM)的原理
    Sql Server LightWeight Pooling(纤程) 选项
    在线 Sql Server 服务无法启动的解决办法
  • 原文地址:https://www.cnblogs.com/pzhblog/p/4573857.html
Copyright © 2011-2022 走看看