精华内容
参与话题
问答
  • 锁机制

    千次阅读 2018-03-02 14:56:58
    概述:在java多线程中,有synchronized关键字来实现线程间的同步互斥工作,那么其实还有一个更优秀的机制去完成这个“同步互斥”工作,他就是Lock对象,用得最多的是重入ReentrantLock和读写...

    概述:

    在java多线程中,有synchronized关键字来实现线程间的同步互斥工作

    那么其实还有一个更优秀的机制去完成这个“同步互斥”工作,他就是Lock对象

    用得最多的是重入锁ReentrantLock读写锁ReentrantReadWriteLock。他们具有比synchronized更为强大的功能,
    并且有嗅探锁定、多路分支等功能。

    重入锁ReentrantLock:

    在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。

    public class UseReentrantLock {
    	
    	private Lock lock = new ReentrantLock();
    	
    	public void method1(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
    			Thread.sleep(1000);
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} finally {
    			
    			lock.unlock();
    		}
    	}
    	
    	public void method2(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2..");
    			Thread.sleep(2000);
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2..");
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} finally {
    			
    			lock.unlock();
    		}
    	}
    	
    	public static void main(String[] args) {
    		final UseReentrantLock ur = new UseReentrantLock();
    		Thread t1 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				ur.method1();
    				ur.method2();
    			}
    		}, "t1");
    
    		t1.start();
    		try {
    			Thread.sleep(10);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }

    读写锁ReentrantReadWriteLock:

    其核心就是实现读写分离的锁。在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁

    synchronized、ReentrantLock,同一时间内,只能有一个线程进行访问被锁定的代码,

    那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。

    口诀:读读共享(只有所有线程都是读才是共享),写写互斥,读写互斥(重要:这个一定得互斥)。

    package com.bjsxt.height.lock021;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
    
    public class UseReentrantReadWriteLock {
    
    	private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    	private ReadLock readLock = rwLock.readLock();
    	private WriteLock writeLock = rwLock.writeLock();
    	
    	public void read(){
    		try {
    			readLock.lock();
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
    			Thread.sleep(3000);
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			readLock.unlock();
    		}
    	}
    	
    	public void write(){
    		try {
    			writeLock.lock();
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
    			Thread.sleep(3000);
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			writeLock.unlock();
    		}
    	}
    	
    	public static void main(String[] args) {
    		
    		final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();
    		
    		Thread t1 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				urrw.read();
    			}
    		}, "t1");
    		Thread t2 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				urrw.read();
    			}
    		}, "t2");
    		Thread t3 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				urrw.write();
    			}
    		}, "t3");
    		Thread t4 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				urrw.write();
    			}
    		}, "t4");		
    		
    		/**
    		 * 两个读锁:
    		 * 当前线程:t2进入...
    		 * 当前线程:t1进入...
    		 * 当前线程:t2退出...
    		 * 当前线程:t1退出...
    		 * 结论:两个都是读锁,可以同时进入
    		 */
    //		t1.start();
    //		t2.start();
    		
    		/**
    		 * 读写两个锁:
    		 * 当前线程:t1进入...
    		 * 当前线程:t1退出...
    		 * 当前线程:t3进入...
    		 * 当前线程:t3退出...
    		 * 结论:读写两个锁:互斥进入,谁先抢到锁,谁先进入,下个线程只有等前一个锁释放了才能进
    		 */
    //		t1.start(); // R 
    //		t3.start(); // W
    		
    		/**
    		 * 两个写锁:
    		 * 当前线程:t3进入...
    		 * 当前线程:t3退出...
    		 * 当前线程:t4进入...
    		 * 当前线程:t4退出...
    		 * 结论:互斥
    		 */
    		t3.start();
    		t4.start();
    	}
    }
    

    锁与等待/通知(Lock替代synchronize,Condition代替wait,notify)
    使用synchronized的时候,如果需要多线程间进行协作工作则需要Object的wait()和notify()、notifyAll()方法进行配合工作。

    那么同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Condition。

    这个Condition一定是针对具体某一把锁的。也就是在只有锁的基础之上才会产生Condition。

    public class UseCondition {
    
    	private Lock lock = new ReentrantLock();
    	private Condition condition = lock.newCondition();
    	
    	public void method1(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
    			Thread.sleep(3000);
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
    			condition.await();	//相当于 Object wait
    			System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行...");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    	public void method2(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
    			Thread.sleep(3000);
    			System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
    			condition.signal();	 //相当于 Object notify
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    	public static void main(String[] args) {
    		final UseCondition uc = new UseCondition();
    		Thread t1 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				uc.method1();
    			}
    		}, "t1");
    		Thread t2 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				uc.method2();
    			}
    		}, "t2");
    		
    		t1.start();
    		t2.start();
    	}
    }
    当前线程:t1进入等待状态..
    当前线程:t1释放锁..
    当前线程:t2进入..
    当前线程:t2发出唤醒..
    当前线程:t1继续执行...

    我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知。
    package com.bjsxt.height.lock020;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class UseManyCondition {
    
    	private ReentrantLock lock = new ReentrantLock();
    	private Condition c1 = lock.newCondition();
    	private Condition c2 = lock.newCondition();
    	
    	public void m1(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待..");
    			c1.await();  //await能释放锁,但是会阻塞在这里
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续..");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    	public void m2(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待..");
    			c1.await();   //await能释放锁,但是会阻塞在这里
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续..");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    	public void m3(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待..");
    			c2.await();   //await会释放锁,但是会阻塞在这里
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续..");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    	public void m4(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
    			c1.signalAll();
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    	public void m5(){
    		try {
    			lock.lock();
    			System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
    			c2.signal();
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    	public static void main(String[] args) {
    		final UseManyCondition umc = new UseManyCondition();
    		Thread t1 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				umc.m1();
    			}
    		},"t1");
    		Thread t2 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				umc.m2();
    			}
    		},"t2");
    		Thread t3 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				umc.m3();
    			}
    		},"t3");
    		Thread t4 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				umc.m4();
    			}
    		},"t4");
    		Thread t5 = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				umc.m5();
    			}
    		},"t5");
    		
    		t1.start();	// c1
    		t2.start();	// c1
    		t3.start();	// c2
    		
    		//过一会再唤醒线程
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
    		t4.start();	// c1
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		t5.start();	// c2
    	}
    }
    当前线程:t1进入方法m1等待..
    当前线程:t3进入方法m3等待..
    当前线程:t2进入方法m2等待..
    当前线程:t4唤醒..
    当前线程:t1方法m1继续..
    当前线程:t2方法m2继续..
    当前线程:t5唤醒..
    当前线程:t3方法m3继续..
    公平锁和非公平锁:
    Lock lock = new ReentrantLock(boolean isFair);  //不公平锁,顺序由CPU定,公平锁,要维护顺序,性能不及不公平锁
    lock用法:
    tryLock(): 尝试获得锁,获得结果用true/false返回。
    tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回。
    isFair():是否是公平锁。
    isLocked():是否锁定。
    getHoldCount(): 查询当前线程保持此锁的个数,也就是调用lock()次数。
    lockInterruptibly():优先响应中断的锁。
    getQueueLength():返回正在等待获取此锁定的线程数。
    getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数。
    hasQueuedThread(Thread thread): 查询指定的线程是否正在等待此锁。
    hasQueuedThreads(): //查询是否有线程正在等待此锁。
    hasWaiters():查询是否有线程正在等待与此锁定有关的condition条件。
    

    优化:
    1 避免死锁
    2 减小锁的持有时间
    3 减小锁的粒度
    4 锁的分离
    5 尽量使用无锁的操作,如原子操作(Atomic系列类),volatile关键字
    展开全文
  • 数据库的锁机制

    万次阅读 2018-04-25 10:14:18
    在并发访问情况下,可能会出现脏读、不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念。并发控制在计算机科学,特别是程序设计、操作系统、多处理机和数据库等...

    在并发访问情况下,可能会出现脏读、不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念。

    并发控制

    在计算机科学,特别是程序设计、操作系统、多处理机和数据库等领域,并发控制(Concurrency control)是确保及时纠正由并发操作导致的错误的一种机制。

    数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。下面举例说明并发操作带来的数据不一致性问题:

    现有两处火车票售票点,同时读取某一趟列车车票数据库中车票余额为 X。两处售票点同时卖出一张车票,同时修改余额为 X -1写回数据库,这样就造成了实际卖出两张火车票而数据库中的记录却只少了一张。 产生这种情况的原因是因为两个事务读入同一数据并同时修改,其中一个事务提交的结果破坏了另一个事务提交的结果,导致其数据的修改被丢失,破坏了事务的隔离性。并发控制要解决的就是这类问题。

    封锁时间戳乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

    当并发事务同时访问一个资源时,有可能导致数据不一致,因此需要一种机制来将数据访问顺序化,以保证数据库数据的一致性。锁就是其中的一种机制。

    在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足。

    锁的分类(oracle)

    一、按操作划分,可分为DML锁DDL锁

    二、按锁的粒度划分,可分为表级锁行级锁页级锁(mysql)

    三、按锁级别划分,可分为共享锁排他锁

    四、按加锁方式划分,可分为自动锁显示锁

    五、按使用方式划分,可分为乐观锁悲观锁

    DML锁(data locks,数据锁),用于保护数据的完整性,其中包括行级锁(Row Locks (TX锁))、表级锁(table lock(TM锁))。 DDL锁(dictionary locks,数据字典锁),用于保护数据库对象的结构,如表、索引等的结构定义。其中包排他DDL锁(Exclusive DDL lock)、共享DDL锁(Share DDL lock)、可中断解析锁(Breakable parse locks)

    展开全文
  • Redis实现分布式锁机制

    万次阅读 2018-11-18 11:17:22
    Redis实现分布式思路  常用的是redis函数是setnx(),这个应该是实现分布式最主要的函数。首先是将某一业务标识名作为键存到redis里,并为其设个过期时间,如果是还有加锁请求过来,先是通过setnx()看看是否能将...

    Redis实现分布式锁思路
      常用的是redis函数是setnx(),这个应该是实现分布式锁最主要的函数。首先是将某一业务标识名作为键存到redis里,并为其设个过期时间,如果是还有加锁请求过来,先是通过setnx()看看是否能将锁的标识插入到redis里,可以的话就返回true,不可以就返回false。
      
    一、使用Lua脚本简单的实现Redis锁的实现 :

    public class DistributeLocker {
    
        private Jedis jedis;
    
        private Lock lock;
    	/**
    	 * 加锁成功标识
    	 */
        private static final String LOCK_SUCCESS = "1";
    	/**
    	 * 脚本入参个数
    	 */
        private static final int KEY_COUNT = 1;
    	/**
    	 * 加锁脚本
    	 */
        private static String LOCK_SCRIPT;
    	/**
    	 * 解锁脚本
    	 */
        private static String RELEASE_SCRIPT;
    	/**
    	 * 初始化脚本
    	 */
        static {
            LOCK_SCRIPT = "local key = redis.call('get',KEYS[1]) if ('1' == key) then return 0 "
                    + "else redis.call('set',KEYS[1],'1') redis.call('expire',KEYS[1],ARGV[1]) return 1 end";
            RELEASE_SCRIPT = "if (redis.call('exists',KEYS[1])) then redis.call('del',KEYS[1]) end";
        }
        /**
    	 * 定义带参构造函数
    	 */
        public DistributeLocker (Lock lock) { }
        public void lock (final String key, final int exprieTime) {
        	try {
    	        if (LOCK_SUCCESS.equals(jedis.eval(LOCK_SCRIPT, KEY_COUNT, key, String.valueOf(exprieTime)))) {
    	            lock.success();
    	        } else {
    	            lock.failure();
    	        }
            } finally {
            	releaseLock(key);
            }
        }
    	
    	/**
    	 * 解锁逻辑
    	 */
        public void releaseLock (String key) {
            jedis.eval(RELEASE_SCRIPT, KEY_COUNT, key);
        }
    }
    
    
    
    public interface Lock {
    
        void success();
    
        void failure();
    }
    
    public class Test {
    
        void purchase(String key, int secondTime){
            new DistributeLocker(new Lock() {
                @Override
                public void success() {
                    // do something
                }
    
                @Override
                public void failure() {
                    // do something
                }
            }).lock(key,secondTime);
        }
    }
    

    注:Redis分布式锁的实现方式有很多种,各有利弊,加锁的时候主要注意检查锁与加锁操作要有原子性,防止重复加锁成功。过期时间主要是为了防止未释放锁导致锁的一直存在,从而无法获取锁操作。

    二、使用Redisson框架

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.13.0</version>
    </dependency>
    
    • 使用缺省配置

    • 工具类

    @Slf4j
    @Component
    public class DistributeLocker {
    
        private RedissonClient redissonClient;
    
        public DistributeLocker(RedissonClient redissonClient) {
            this.redissonClient = redissonClient;
        }
    
        public void lock (String key, long exprieTime, LockTask task) {
            lock(0L, key, exprieTime, task);
        }
    
        public void lock (long waitTime, String key, long exprieTime, LockTask task) {
            RLock rLock = redissonClient.getLock(key);
            try {
                if (rLock.tryLock(waitTime, exprieTime, TimeUnit.MILLISECONDS)) {
                    task.success();
                } else {
                    task.failure();
                }
            } catch (Exception e) {
                log.error("DistributeLocker.lock Error. key:{}, e:{}", key, e);
            } finally {
                rLock.unlock();
            }
        }
    
    }
    
    
    • 使用
    @SpringBootApplication
    @EnableDiscoveryClient
    public class StartUpApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext context = SpringApplication.run(StartUpApplication.class, args);
            DistributeLocker locker = context.getBean(DistributeLocker.class);
            locker.lock("test", 100L, new LockTask() {
                @Override
                public void success() {
                    System.out.println("加锁成功");
                }
    
                @Override
                public void failure() {
                    System.out.println("加锁失败");
                }
            });
        }
    }
    
    展开全文
  • 【7】Java锁机制

    万次阅读 2019-11-26 20:49:47
    知识点3:悲观、乐观 1、场景 2、悲观与乐观 (1)悲观 (2)乐观 知识点4:原子类 1、为什么会有原子类 2、如果同一个变量要被多个线程访问,则可以使用该包中的类 3、CAS无锁模式 (1)什么...

    目录

    知识点1:重入锁

    知识点2:读写锁

    知识点3:悲观锁、乐观锁

    1、场景

    2、悲观锁与乐观锁

    (1)悲观锁

    (2)乐观锁

    知识点4:原子类

    1、为什么会有原子类

    2、如果同一个变量要被多个线程访问,则可以使用该包中的类

    3、CAS无锁模式

    (1)什么是CAS

    (2)CAS算法理解

    4、常用原子类

    5、CAS(乐观锁算法)的基本假设前提

    6、CAS缺点

    知识点5:分布式锁


    知识点1:重入锁

    锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利。

    重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
    在JAVA环境下 ReentrantLock(显式锁、轻量级锁)和Synchronized (内置锁、重量级锁)都是 可重入锁。

    public class Test implements Runnable {
    	public  synchronized void get() {
    		System.out.println("name:" + Thread.currentThread().getName() + " get();");
    		set();
    	}
    
    	public synchronized  void set() {
    		System.out.println("name:" + Thread.currentThread().getName() + " set();");
    	}
    
    	@Override
    
    	public void run() {
    		get();
    	}
    
    	public static void main(String[] args) {
    		Test ss = new Test();
    		new Thread(ss).start();
    		new Thread(ss).start();
    		new Thread(ss).start();
    		new Thread(ss).start();
    	}
    }
    
    public class Test02 extends Thread {
    	ReentrantLock lock = new ReentrantLock();
    	public void get() {
    		lock.lock();
    		System.out.println(Thread.currentThread().getId());
    		set();
    		lock.unlock();
    	}
    	public void set() {
    		lock.lock();
    		System.out.println(Thread.currentThread().getId());
    		lock.unlock();
    	}
    	@Override
    	public void run() {
    		get();
    	}
    	public static void main(String[] args) {
    		Test ss = new Test();
    		new Thread(ss).start();
    		new Thread(ss).start();
    		new Thread(ss).start();
    	}
    
    }
    

    知识点2:读写锁

    相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(译者注:也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。尽管如此,我们还是应该了解其实现背后的原理。

    public class Cache {
    	static Map<String, Object> map = new HashMap<String, Object>();
    	static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    	static Lock r = rwl.readLock();
    	static Lock w = rwl.writeLock();
    
    	// 获取一个key对应的value
    	public static final Object get(String key) {
    		r.lock();
    		try {
    			System.out.println("正在做读的操作,key:" + key + " 开始");
    			Thread.sleep(100);
    			Object object = map.get(key);
    			System.out.println("正在做读的操作,key:" + key + " 结束");
    			System.out.println();
    			return object;
    		} catch (InterruptedException e) {
    
    		} finally {
    			r.unlock();
    		}
    		return key;
    	}
    
    	// 设置key对应的value,并返回旧有的value
    	public static final Object put(String key, Object value) {
    		w.lock();
    		try {
    
    			System.out.println("正在做写的操作,key:" + key + ",value:" + value + "开始.");
    			Thread.sleep(100);
    			Object object = map.put(key, value);
    			System.out.println("正在做写的操作,key:" + key + ",value:" + value + "结束.");
    			System.out.println();
    			return object;
    		} catch (InterruptedException e) {
    
    		} finally {
    			w.unlock();
    		}
    		return value;
    	}
    
    	// 清空所有的内容
    	public static final void clear() {
    		w.lock();
    		try {
    			map.clear();
    		} finally {
    			w.unlock();
    		}
    	}
    
    	public static void main(String[] args) {
    		new Thread(new Runnable() {
    
    			@Override
    			public void run() {
    				for (int i = 0; i < 10; i++) {
    					Cache.put(i + "", i + "");
    				}
    
    			}
    		}).start();
    		new Thread(new Runnable() {
    
    			@Override
    			public void run() {
    				for (int i = 0; i < 10; i++) {
    					Cache.get(i + "");
    				}
    
    			}
    		}).start();
    	}
    }
    

    知识点3:悲观锁、乐观锁

    1、场景

    当多个请求同时操作数据库时,首先将订单状态改为已支付,在金额加上200,在同时并发场景查询条件下,会造成重复通知。

    SQL:

    Update

    2、悲观锁与乐观锁

    (1)悲观锁

    总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。

    (2)乐观锁

    总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。

     

     version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

    核心SQL语句

    update table set x=x+1, version=version+1 where id=#{id} and version=#{version};   

     

    CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。


    知识点4:原子类

    java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程

    原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。

    1、为什么会有原子类

    CAS:Compare and Swap,即比较再交换。

    jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

    2、如果同一个变量要被多个线程访问,则可以使用该包中的类

    AtomicBoolean

    AtomicInteger

    AtomicLong

    AtomicReference

    3、CAS无锁模式

    (1)什么是CAS

    CAS:Compare and Swap,即比较再交换。

    jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

    (2)CAS算法理解

    (1)与锁相比,使用比较交换(下文简称CAS)会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。

    (2)无锁的好处:

    第一,在高并发的情况下,它比有锁的程序拥有更好的性能;

    第二,它天生就是死锁免疫的。

    就凭借这两个优势,就值得我们冒险尝试使用无锁的并发。

    (3)CAS算法的过程是这样:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。

    (4)CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

    (5)简单地说,CAS需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,那说明它已经被别人修改过了。你就重新读取,再次尝试修改就好了。

    (6)在硬件层面,大部分的现代处理器都已经支持原子化的CAS指令。在JDK 5.0以后,虚拟机便可以使用这个指令来实现并发操作和并发数据结构,并且,这种操作在虚拟机中可以说是无处不在。

    4、常用原子类

    Java中的原子操作类大致可以分为4类:原子更新基本类型、原子更新数组类型、原子更新引用类型、原子更新属性类型。这些原子类中都是用了无锁的概念,有的地方直接使用CAS操作的线程安全的类型。

    AtomicBoolean

    AtomicInteger

    AtomicLong

    AtomicReference

    public class Test0001 implements Runnable {
    	private static Integer count = 1;
    	private static AtomicInteger atomic = new AtomicInteger();
    
    	@Override
    	public void run() {
    		while (true) {
    			int count = getCountAtomic();
    			System.out.println(count);
    			if (count >= 150) {
    				break;
    			}
    		}
    	}
    
    	public synchronized Integer getCount() {
    		try {
    			Thread.sleep(50);
    		} catch (Exception e) {
    			// TODO: handle exception
    		}
    
    		return count++;
    	}
    
    	public Integer getCountAtomic() {
    		try {
    			Thread.sleep(50);
    		} catch (Exception e) {
    			// TODO: handle exception
    		}
    		return atomic.incrementAndGet();
    	}
    
    	public static void main(String[] args) {
    		Test0001 test0001 = new Test0001();
    		Thread t1 = new Thread(test0001);
    		Thread t2 = new Thread(test0001);
    		t1.start();
    		t2.start();
    	}
    
    }
    

    5、CAS(乐观锁算法)的基本假设前提

    CAS比较与交换的伪代码可以表示为:

    do{   
          
    备份旧数据;  
          
    基于旧数据构造新数据;  
    }while(!CAS(
    内存地址,备份的旧数据,新数据 ))  

     

    (上图的解释:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。)

    就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的 commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。

    public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        }
    
    /** 
    	 * Atomically increments by one the current value. 
    	 * 
    	 * @return the updated value 
    	 */  
    	public final int incrementAndGet() {  
    	    for (;;) {  
    	        //获取当前值  
    	        int current = get();  
    	        //设置期望值  
    	        int next = current + 1;  
    	        //调用Native方法compareAndSet,执行CAS操作  
    	        if (compareAndSet(current, next))  
    	            //成功后才会返回期望值,否则无线循环  
    	            return next;  
    	    }  
    	}  
    

    6、CAS缺点

    CAS存在一个很明显的问题,即ABA问题。

    问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?

    如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。

     


    知识点5:分布式锁

    如果想在不同的jvm中保证数据同步,使用分布式锁技术。

    有数据库实现、缓存实现、Zookeeper分布式锁;

     

    展开全文
  • 数据库锁机制

    万次阅读 多人点赞 2016-08-15 12:38:50
    看到网上大多语焉不详(尤其更新),所以这里做个简明解释,为下面描述方便,这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一个线程,T2 为另一个线程。T3,T4以此类推。下面以SQL Server...
  • MySQL锁机制和PHP锁机制

    千次阅读 2018-08-13 11:16:00
    正文内容 模拟准备--如何模拟高并发访问一个脚本:apache安装文件的bin/ab.exe可以模拟并发量  -c 模拟多少并发量 -n 一共请求多少次 http://请求的脚本 ... ...MYSQL中的: 语法 : LOCK TABLE 表名1 ...
  • Java锁机制

    千次阅读 2018-05-16 00:03:00
    对于熟悉java多线程并发的人来说,java锁机制是不可逃避的话题。那么什么是java锁机制,以及什么时候使用java的锁呢?让我们看看以下几种场景吧!一、同步锁案例:假设现在我们现在有很多人去商店买衣服,因为我们每...
  • 有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。 Hibernate支持悲观锁和乐观锁两种锁机制。悲观锁,顾名思义悲观的认为在...
  • MySQL数据库锁机制

    千次阅读 2018-11-27 11:22:22
    是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所在有数据库必须...
  • Mysql锁机制简单了解一下

    万次阅读 多人点赞 2018-06-07 20:52:11
    Mysql为了解决并发、数据安全的问题,使用了锁机制。 可以按照锁的粒度把数据库锁分为表级锁和行级锁。 表级锁: Mysql中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单 ,资源消耗也比较少,加锁快...
  • java锁机制Synchronized

    千次阅读 2010-12-17 09:04:00
    java锁机制Synchronized
  • 数据库事务的定义 数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。 ...
  • Mysql锁机制

    万次阅读 2018-06-18 00:17:00
    数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库由于其...
  • 有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。 Hibernate支持悲观锁和乐观锁两种锁机制。悲观锁,顾名思义悲观的认为在...
  • 深入解析Java锁机制

    千次阅读 2018-11-20 08:48:58
    转载自:...amp;mid=2247485524&amp;idx=1&amp;sn=2807a248ab60ce21b22dc07ec1b0ee0c&amp;chksm=fbb281aaccc508bc404611ee11b057bf4b3e02fbbb2916c472fe586cf9ee989eab...
  • innodb引擎锁机制和myisam引擎锁机制的区别 1、锁机制(sql操作是需要锁的,select是读锁,update、insert、delete是写锁)的最大区别及用法: (1)myisam只支持表锁: ● 共享锁(读锁、s锁):其他线程操作...
  • 内核中的锁机制

    千次阅读 2016-10-25 11:29:43
    内核多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理分类(1)原子操作atomic_t数据类型,atomic_inc(atomic_t *v)将v加1 ...
  • java的锁机制

    万次阅读 多人点赞 2011-05-19 15:22:00
    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的(一个对象只有一把); 如果这个时候同步对象的被其他线程拿走了,他(这个线程)就只能...
  • JAVA中锁机制synchronized

    2014-05-05 13:59:17
    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的(一个对象只有一把); 如果这个时候同步对象的被其他线程拿走了,他(这个线程)就只能等了...
  • Oracle lock 锁机制 总结

    千次阅读 2012-04-11 19:59:00
    对于lock这个词,要从两方面理解,首先,它代表的是一种控制机制;...lock structure();enqueue(排队机制) resource和lock是数据结构,enqueue是使用的算法。 下面看一下lock的组成结构图: resource struc

空空如也

1 2 3 4 5 ... 20
收藏数 29,984
精华内容 11,993
关键字:

锁机制