synchronized和ReentrantLock浅析

线程安全

简单的说,多个线程同时操作一个数据就有可能造成数据不一致,这就是线程不安全。

线程安全发生的前提:
1.在多线程环境中
2.存在共享数据

只要在这种情况下,多个线程同时修改一个共享数据就可能存在线程安全问题。

看一下demo代码

package test;

public class ThreadTest implements Runnable {
	static int count=0;
	
	public static void main(String[] args) {
		ThreadTest tt=new ThreadTest();
		Thread t1=new Thread(tt);
		Thread t2=new Thread(tt);
		Thread t3=new Thread(tt);
		t1.start();
		t2.start();
		t3.start();
		
		try {
			Thread.sleep(10000);//确保所有线程执行完
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(count);
		
		
	}
	
	
	public void run()
	{
		int n=1000;
		while(n>0)
		{
			count++;
			n--;
		}
		
	}

}

运行结果
在这入图片描述
可以看到结果不是3000,是一个小于3000的数,这就出现了线程安全问题。

在java中线程有两种常见的解决线程安全问题的方法。

1.synchronized关键字

在java中每一个对象都可以是一个锁,这也是synchronized实现同步的基础。
1.修饰普通方法时,锁是当前的实例对象,也就是this对象。(也称对象锁)
2.修饰静态方法时,锁是当前类的字节码对象,也就是xxx.class对象。(也称类锁)
3.修饰同步方法块时,锁是括号内的对象,给指定的对象加锁。
在进入同步代码块时,首先就要获取锁!

synchronized锁是可重入的,可重入的意思就是同一个线程拥有某个对象的锁时,再次申请仍会获得该对象的锁,也就说synchronized锁是可重入的。
注意synchronized是关键字,它不是锁!

代码演示

package test;

public class ThreadTest implements Runnable {
	static int count=0;
	
	public static void main(String[] args) {
		ThreadTest tt=new ThreadTest();
		Thread t1=new Thread(tt);
		Thread t2=new Thread(tt);
		Thread t3=new Thread(tt);
		t1.start();
		t2.start();
		t3.start();
		
		try {
			Thread.sleep(10000);//确保所有线程执行完
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(count);
		
	}
	
	
	public synchronized void run()
	{
		int n=1000;
		while(n>0)
		{
			count++;
			n--;
		}
		
	}

}

结果为在这里插入图片描述
和预期一样,线程安全,为3000.

2.ReetrantLock

ReetrantLock实现了lock接口,是一种比较常见的解决线程安全问题的锁,它也是可重入的。
先看代码演示

package test;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo implements Runnable {
	static int count=0;
	private static ReentrantLock reetrantLock=new ReentrantLock();
	public static void main(String[] args) {
		ReetrantLockDemo tt=new ReentrantLockDemo();
		Thread t1=new Thread(tt);
		Thread t2=new Thread(tt);
		Thread t3=new Thread(tt);
		t1.start();
		t2.start();
		t3.start();
		
		try {
			Thread.sleep(10000);//确保所有线程执行完
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(count);
		
	}
	
	
	public void run()
	{
		reetrantLock.lock();
		int n=1000;
		while(n>0)
		{
			count++;
			n--;
		}
		reetrantLock.unlock();
		
	}

}

结果为
在这里插入图片描述
接下来谈论reentrantLock的一些特性
加锁,解锁
公平锁,不公平锁

首先看加锁,解锁的源码

 public void lock() {
        sync.lock();
    }
public void unlock() {
        sync.release(1);
    }

可以看出底层是sync对象调用的lock(),release()操作来完成的

private final Sync sync;

而Sync是一个用final修饰的私有成员变量,所以必须用构造函数初始化

public ReentrantLock() {
        sync = new NonfairSync();
    }
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

从这里就可以看出当你使用无参构造函数,或者传一个false的时候你创建的就是不公平锁,传一个true的参数时就是公平锁。

公平锁与不公平锁的竞争机制有区别。
当线程进入排队队列的时候公平锁与不公平锁没有区别,都是按顺序唤醒进程,但是当h锁刚被释放的时候,有一个新线程进来,它就会和排在head的线程竞争而不是直接进入排队队列,并且有可能胜利,所以就造成了不公平。而对于公平锁它会直接进入排队队列。

公平锁大部分时间没有必要使用,只有在业务必须要求公平的时候才使用,synchronized是不公平锁

关于可重入
reentrantLock里面有一个字段state,但线程占有锁的时候state加1,因为reentrantLock是可重入的,所以当同一个线程再次申请这个锁的时候,它能成功,此时state再次加1。释放锁时,也是同样释放成功state减1.。当state为0 的时候,它才会去队列里唤醒下一个线程。

 

synchronized和ReetrantLock的一些区别
  • synchronized是关键字,ReetrantLock是类
  • ReetrantLock能够实现比synchronized更加细粒的控制,比如公平性
  • ReetrantLock可以对获取锁的等待时间进行设置 ,避免死锁
  • ReetrantLock调用lock获取锁后,要用unlock解锁
  • ReetrantLock可以获取各种锁的信息
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值