多线程的锁
关于线程同步的问题
-
线程同步是为了解决并发问题的,由于同一进程的多个线程共享同一块存储空间的情况下会带来一些访问的冲突问题,所以为了保证数据在方法中被访问时的正确性,我们会提前加入锁机制最常用的一开始就是synchronized关键字,当线程获得对象的独占锁便独占资源,让其他线程必须等待,使用完毕后再释放锁。
-
在使用synchronized关键字会产生一些问题:
- 一个线程持有锁会导致其他此锁的线程挂起等待其释放锁
- 在多线程的竞争下,加锁和释放锁的操作会导致比较多的上下文切换和调度延时,产生性能问题
- 如果一个优先级的高的线程等待一个优先级较低的线程释放锁会导致性能倒置问题
-
关于synchronized的一些用法:
- 首先可以在方法上添加关键字synchronized也可以使用静态代码块,如下代码:
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
/**
* synchronized锁的对象是方法的调用者
* 两个方法用的是同一把锁,谁先拿到谁执行
*/
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
- 聊聊关于Lock锁的用法
- 从JDK5.0开始,Java提供了更强大的线程同步机制也就是通过显示定义同步锁对象来实现同步
- java.util.concurrent.locks.Lock接口提供的Lock锁,它提供了对共享资源的独占访问
- Lock的实现类最重要的就是ReentrantLock类,也就是可重入锁,可以手动加锁,释放锁,以下是关于Lock锁的一些用法:这里举了买票的例子,使用了Lock锁
package highGrade;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试Lock锁
* @author 87682
*/
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums = 10;
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//加锁
lock.lock();
if(ticketNums>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else {
break;
}
}finally {
//解锁
lock.unlock();
}
}
}
}
- 谈谈关于synchronized和Lock锁的对比
- synchronized是内置的Java关键字,Lock是一个Java类
- Lock锁是显示的锁,也就是需要手动开启和关闭,如果不释放锁会产生死锁情况, synchronized是隐式锁,出了作用域会自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的实践来调度线程,性能更优,而且具有更好的扩展性,提供了更多的子类,包括读写锁可以更加细粒度的控制线程。
- 优先的使用顺序:Lock > 同步代码块(已经进入了方法体分配了资源) > 同步方法(在方法体之外)
- synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁(使用lock.trylock())
- synchronized是可重入锁,不可以中断,是非公平锁,Lock是可重入锁,可以自己设置是非公平锁还是公平锁
- synchronized可能会产生线程的阻塞导致其他线程不断处于等待状态,Lock锁就不会一直等待下去
- synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码
如何判断锁的目标是谁
- 首先synchronized锁的对象是方法的调用者,哪个方法先拿到锁就先执行谁
- 如果是静态同步方法的话我们需要注意的是锁的是Class对象,自始至终就是类被加载的Class对象,无论实例化多少对象,锁住的也是Class类模板