java线程(二)线程安全问题与锁介绍

线程安全问题:多个线程之间共享统一资源且操作共享数据导致的问题。
简单来说就是:当一个线程在执行并操作共享数据时,其他线程参与了共享数据的读写,就可能会导致线程安全问题的产生。
可以模拟一个汽车站卖票的场景,假设汽车站一次只能卖十张票,分三个窗口在卖票,此时可以理解为三个线程操作共享数据,过程如下:
下图中令线程沉睡一秒有利于线程安全问题的暴露

执行线程:

此时可以看到3号票卖出去了两张分别是窗口一与窗口二,这是因为线t2获取cpu资源后操作共享数据(此时count的值为3),此时线程t2还未执行count--时,该线程执行时间结束。
然后线程t1争取到CPU资源开始执行代码,此时线程一获取的count值仍是3不是2。这就是线程安全问题。
线程安全问题可以用java提供的锁来解决,比如用synchronized同步块修改MyThread类

Java实现锁有两种语法,一种是synchronized关键字,另外一种是Lock关键字。

1.synchronized
1.1synchronized修饰代码块锁一段代码
在某一时刻只能有一个线程执行代码块中的内容(count++),其他试图访问该对象的线程将被阻塞,从而保证线程安全。

1.21synchronized修饰静态方法锁class对象
静态方法是属于类的而不属于对象的。所以synchronized修饰的静态方法锁定的是这个类的所有对象

1.3使用同步关键字加锁对象的实例方法

2.Lock
Lock的实现主要有ReentrantLock(重入锁)、ReentrantReadWriteLock
2.1ReentrantLock重入锁
重进入:
重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,也就是说需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则不会阻塞,再次获取成功。
锁的最终释放条件:线程重复n次获取了锁,随后在第n次释放该锁后,其它线程能够获取到该锁。
锁的最终释放要求锁对于获取进行计数自增,计数表示当前线程被重复获取的次数,而被释放时,计数自减,当计数为0时表示锁已经成功释放。
从重进入这个特点来看他就比synchronized要更加灵活,效率更高。
另外重入锁可以判断当前线程能不能拿到锁,如果拿不到可以执行其他逻辑,但是synchronized中没有拿到锁的线程只能阻塞等待。
以下是一个实例

  /**
  * ReentrantLock重入锁(如果当个前需要获取锁的线程与当前占据锁的线程相同,则不用阻塞,直接获取锁)
  * @author wangshuai 
  *ReentrantLock重入锁与synchronized锁区别是重入锁效率更好,重入锁可以判断当前线程能不能拿到锁,如果拿不到可以执行
  *其他逻辑,但是synchronized中没有拿到锁的线程只能阻塞等待。
  */

public class ReentrantLock_demo {
private static int count;
//重入锁
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new MyRunnable(),"AAA");
Thread t2 = new Thread(new MyRunnable(),"BBB");
//计算耗时
long starttime = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
long endtime = System.currentTimeMillis();
System.out.println("总耗时:"+(endtime-starttime));
}
static class MyRunnable implements Runnable{
public void increase(){
//加锁(这个可以锁代码)(并发编程)(如果此时代码出异常了,就不会解锁了,所以下面需要手动解锁)
lock.lock();
try {
count++;
//模拟一个线程拿到锁以后另外的线程只能等待他完成并释放锁才能执行,效率差
System.out.println(Thread.currentThread().getName()+"计算中");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"计算完成");
} catch (Exception e) {
// TODO: handle exception
}finally {
//解锁
lock.unlock();
}
}
public void increase1(){
//模拟一个线程拿到锁以后另外的线程可以判断是否能拿到锁,拿不到则执行分支代码,效率高
//判断当前线程能不能够拿到锁
if(lock.tryLock()){
try {
count++;
System.out.println(Thread.currentThread().getName()+"计算中");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"计算完成");
} catch (Exception e) {
// TODO: handle exception
}finally {
//解锁
lock.unlock();
}
}else{
//加锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"识别到锁并不在空闲中,当前线程执行其他内容");
count++;
//解锁
lock.unlock();
}
}
@Override
public void run() {
for(int i=0;i<5;i++){
increase();
}
}
}
}

   经测试线程执行体中执行increase1方法耗时小于increase方法,因为increase1方法中线程如果拿不到锁可以执行其他逻辑。

   2.2ReentrantReadWriteLock是Lock的另一种实现方式,ReentrantReadWriteLock中包含读锁(共享锁)和写锁(排它锁),
        读锁保证在同一时刻可以有多个读线程进行读操作,写锁保证在同一时刻只会有一个线程进行写入操作,且后续过来的读写锁获取操作都会阻塞。
        相比于ReentrantLock的排它锁来说,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。
        实际应用中大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,
        这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。
        例子:
public class ReadWriteLockDemo {
public static void main(String[] args) {
    /*创建ReadWriter对象*/
    final ReadWrite readWrite = new ReadWrite();
    /*创建启动三个读线程*/
    for(int i=0;i<3;i++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                readWrite.get();    
            }
        }).start();
        /*创建三个写线程*/
        new Thread(new Runnable() {
            @Override
            public void run() {
                /*随机写一个数*/
                readWrite.put(new Random().nextInt(8));
            }
        }).start();
    }
}

}
class ReadWrite{
//共享数据,只能创建一个线程写数据,可以多个线程读数据
private Object data = null;
//创建一个读写锁
ReadWriteLock rwlock = new ReentrantReadWriteLock();
/**
* 读锁,可以多个线程同时读取,只是保证在读的时候没有写锁在运行
/
public void get(){
//上读锁
rwlock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"准备读数据");
/
休眠/
Thread.sleep((long)(Math.random()
1000));
System.out.println(Thread.currentThread().getName()+"读出的数据为"+data);

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            rwlock.readLock().unlock();
        }
    }
    /**
     * 写数据,多个线程不能同时写,所以必须上写锁
     */
    public void put(Object data){
        /*上写锁*/
        rwlock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"准备写数据!");
            /*休眠*/
            Thread.sleep((long)(Math.random() * 1000));
            this.data = data;
            System.out.println(Thread.currentThread().getName()+"写入的数据"+data);
        } catch (Exception e) {
        }finally {
            rwlock.writeLock().unlock();
        }
    }

}

结果:

从结果中可以看出三个读数据的线程同时执行,并没有互相排斥(一个执行完才执行下一个)。而写锁则是一个一个执行,并没有同时执行。
利用读写锁编写一个简单的缓存机制:
/**

  • 读写锁的实例:缓存
  • 缓存的机制:我根据key从集合中取值,如果有直接取,没有就查询并赋值后在取
  • @author wangshuai

/
public class CacheDemo {
//创建一个缓存对象
static Map<String,Object> cache = Collections.synchronizedMap(new HashMap<String,Object>());
//创建一个读写锁
static ReadWriteLock rwlock = new ReentrantReadWriteLock();
/
*
* 定义一个获取数据的方法(有值则取,无值则赋值)
*/
public Object getData(String key){
//开启读锁
rwlock.readLock().lock();
//获取到缓存对象中的数据
Object value = null;
try{
value = cache.get(key);
//如果没有这个值
if(value==null){
//关闭读锁
rwlock.readLock().unlock();
//开启写锁
rwlock.writeLock().lock();
try{
value = cache.get(key);
//在次判断是否为空(因为可能有另一个线程在此时已经给这个key赋值了)
if(value == null){
//把数据存到缓存中
System.out.println(Thread.currentThread().getName()+"从DB获取数据,key值为:"+key);
cache.put(key, "key的值");
}
}finally{
//释放写锁
rwlock.writeLock().unlock();
}
//打开读锁
rwlock.readLock().lock();
}
value = cache.get(key);
System.out.println(Thread.currentThread().getName()+"从缓存获取数据:"+value.toString());
}finally {
//关闭读锁
rwlock.readLock().unlock();
}
return value;
}
static class CacheRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<3;i++){
new CacheDemo().getData(i+"");
}
}
}
public static void main(String[] args) {
//放入数据到缓存
cache.put("1", "111");
for(int i=0;i<3;i++){
new Thread(new CacheRunnable(),"AAA"+i).start();
}
}
}
结果:

posted @ 2019-03-01 08:56  小小短腿儿  阅读(152)  评论(0)    收藏  举报