一 java-锁 目的
目的:防止多线程执行某段代码时导致的数据异常,互相干扰,所以把某段代码块加上锁,保证其原子性
二 使用 synchronized关键字 (java语言内置锁)
2.1 单独使用于对象,使用对象锁
object object_lock = new object();
public void run()
{
//被锁住的代码块,保证了原子性,同一时间只能被一个线程所操作
synchronized(object_lock){
do something;
}
}
Java中的每个对象都有一个监视器,来监测并发代码的重入,上面就synchronized获取了lock的监视器,
2.2 synchronized 用于方法,
public class Thread1 implements Runable{
public synchronized void run()
{
do something
}
}
其实是获取了Thread1的监视器,对象锁。
线程同步方法是通过锁来实现,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,
其他访问该对象的线程就无法再访问该对象的其他同步方法。
三 java 显示锁
2.1 第一种显示锁 Lock
实现为ReentrantLock() ,是语法层级的锁,
常用方法有
lock();//获取锁
unlock();//释放锁
trylock();//尝试获取锁
使用范式为
//显示锁
Lock lock = new ReentrantLock();
//获取锁
lock.lock();
try {
//执行业务
} finally {
//释放锁
lock.unlock();
}
获取锁之后,一定要在finally中释放掉,
Lock与synchronized的比较
1 synchronized 代码简洁
2 Lock获取锁可以被中断,可以设置超时,可以尝试获取锁
如果业务场景满足2中这几点,可以考虑用Lock,否则就用synchronized。
2.2 第2种显示锁 读写锁 ReadWriteLock 接口
实现为 ReentrantReadWriteLock
概念:同一时刻允许多个线程同时访问,但是写线程访问的时候,所有线程都被阻塞
最适用于读多写少情况,比普通线程性能几何倍增加
用法:
//读写锁对象
ReadWriteLock myLock = new ReentrantReadWriteLock();
//读锁
Lock readLock = myLock.readLock();
//写锁
Lock writeLock = myLock.writeLock();
然后在多线程中,读的地方用读锁,写的地方用写锁即可。
2.3 Lock 里面的等待和通知
实现:
Lock lock = new ReentrantReadWriteLock();
Condition kmCondition = lock.newCondition();
kmCondition.await();//等待
kmCondition.signal();//通知
demo 等待快递地址,和公里数变化
**
* 快递信息类
*
* @author hup
* @since 2018-09-10 22:30
*/
public class Express {
/**
* 变化公里数
*/
private int km = 0;
/**
* 地址
*/
private String site = "上海";
//显示锁
private Lock lock = new ReentrantLock();
private Condition kmCondition = lock.newCondition();
private Condition siteCondition = lock.newCondition();
//公里数变化
public void changeKm() {
lock.lock();
try {
this.km = 102;
//通知
kmCondition.signal();
} finally {
lock.unlock();
}
}
//地址变化
public void changeSite() {
lock.lock();
try {
this.site = "北京";
//通知
siteCondition.signal();
} finally {
lock.unlock();
}
}
/**
* 等待公里数变化
*/
public void waitKm() {
lock.lock();
try {
while (km < 100) {
try {
//等待
kmCondition.await();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "公里数发生变化,现在公里数=" + km);
} finally {
lock.unlock();
}
}
/**
* 等待地址变化
*/
public void waitSite() {
lock.lock();
try {
while (site.equalsIgnoreCase("上海")) {
try {
//等待
siteCondition.await();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "地址发生变化,现在地址=" + site);
} finally {
lock.unlock();
}
}
}
/**
* 测试类
* @author hup
* @since 2018-09-20 22:40
*/
public class mainTest {
//快递信息
Express express = new Express();
/**
* 监控公里数变化线程
*/
private class kmThread implements Runnable {
@Override
public void run() {
express.waitKm();
}
}
/**
* 监控地址变化线程
*/
private class siteThread implements Runnable {
@Override
public void run() {
express.waitSite();
}
}
@Test
public void test() {
//3个监控公里数变化的线程
for (int i = 0; i < 3; i++) {
new Thread(new kmThread()).start();
}
//3个监控地址变化的线程
for (int i = 0; i < 3; i++) {
new Thread(new siteThread()).start();
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
//改变公里数
express.changeKm();
}
}
如上面代码 test()方法,我们分别启动了6个线程,去等待公里数和地址的变化,然后我们改变了公里数,
最终输出结果是 Thread-0公里数发生变化,现在公里数=102
如果有多个业务场景下都需要通知线程,可以用一个锁申明初多个condition,在每个业务场景下用各自的condition。
三 名次解释
3.1 可重入锁
就是可以被多次获取的锁
如
public synchronized void testA()
{
System.out.println("A方法执行业务");
testB();
}
public synchronized void testB()
{
System.out.println("B方法执行业务");
}
testA本身有一把锁,调用了testB()又一把锁,共2把锁,即多次获取了锁,叫重入锁。
public void lockTest() {
//获取锁
lock.lock();
try {
System.out.println("第一次获取了锁");
lock.lock();
try {
System.out.println("第二次获取了锁");
} finally {
//释放锁
lock.unlock();
}
} finally {
//释放锁
lock.unlock();
}
}
上面例子里,lock也可以获取2次锁,lock也是可重入锁
由此可见,synchronized 和Lock都属于可重入锁
3.2 公平锁和非公平锁
公平锁的概念:如果在时间上,先对锁进行获取的请求一定先被满足,这个锁就是公平锁(即先等先得),反之为非公平锁(随机给锁)
一般是Lock构造器默认的非公平锁,如果需要公平锁也可指定。
效率对比
非公平锁效率更高,为什么? 请看下面解释
时刻1 A线程 获取到了锁
时刻2 B线程此时也来请求拿锁 被阻塞,被操作系统挂起
时刻3 A线程执行完业务,释放了锁
时刻3 C线程也来请求锁
非公平锁工作机制
此时计算机把锁给C而不是给B
公平锁工作机制
此时计算机把锁给B而不是给A
但是此时B是挂起状态,所以首先要把线程B从挂起状态恢复为正常状态(这个过程会耗费较长的时间)
用非公平锁,把锁给C以后,C执行完业务后,可能刚好B从挂起状态恢复为正常(有这个概率),相对公平锁来说,就有机会减少唤醒挂起线程的这段时间,
所以我们说非公平说效率更高。
当然具体用哪种要根据业务,比如某些优先级高的业务,就想要优先执行,也可以用公平锁。
原文:https://www.cnblogs.com/hup666/p/11886158.html