锁机制(重重重要)
引例
public static void main(String[] args) {
// 并发:多线程操作同一资源,把资源类丢入线程
Ticket ticket = new Ticket();
// 匿名内部类实现
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
}
}).start();
// jdk1.8 lambda表达式代替匿名内部类 :(参数)->{代码}
new Thread(()->{
for (int i = 0; i < 60; i++){
ticket.sale();
} }, "A线程").start();
new Thread(()->{
for (int i = 0; i < 60; i++){
ticket.sale();
}}, "B线程").start();
}
}
// 线程作为一个单独的资源类,需要没有任何附属的操作,这种Implements的方式在实际过程中不会采用
class MyThread implements Runnable {
@Override
public void run() {
}
}
class Ticket{
private int number = 50;
public void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
}
}
- 出现多线程抢夺资源的情况,利用synchronized关键字解决
synchronized关键字
class Ticket{
private int number = 50;
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
}
}
Lock锁
- 代码
class Ticket2{
private int number = 50;
// lock锁
Lock lock = new ReentrantLock();
// 使用锁
public void sale() {
lock.lock();
try {
// 业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- ReentrantLock源码中的公平锁和非公平锁
- 公平锁:十分公平,先来先到
- 非公平锁:十分不公平,可以插队(默认是非公平锁)
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Lock锁和synchronized的区别
- synchronized是内置关键字,Lock是一个类
- synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
- synchronized会自动释放锁,Lock需要手动释放(unlock),如果不释放锁,会产生死锁。
- synchronized 线程1(获取锁,如果阻塞)、那么线程2(就会一直等待);Lock锁就不一定会等待下去(有一个lock.trylock()方法)
- synchronized 是可重锁,非公平,不可以中断;Lock锁是重入锁,可以判断锁,非公平(可以根据自己设置)
- synchronized适合锁少量的代码同步问题,Lock适合代码量多的
- 自动挡和手动挡的区别,手动挡更灵活
锁是什么,如何判断锁的是谁
引例:生产者和消费者问题
- 面试需要手写的:单例模型、排序算法、生产者和消费者、死锁
- 线程之间的通信问题:生产者和消费者问题
- 线程交替执行 线程A 线程B 操作同一个变量 num=0
– 线程A num+1
– 线程B num-1
– 如果线程A和线程B之间不通信的话,那就无法交流num的变化 - 那么线程之家通信的操作:等待唤醒、通知唤醒
synchronized版的*生产者和消费者问题
public class pc {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A线程").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B线程").start();
}
}
// 逻辑: 判断等待,业务,通知
// 资源类
class Data {
private int number = 0;
public synchronized void increment() throws InterruptedException {
if (number != 0) {
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,加1操作完成
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,减1操作完成
this.notifyAll();
}
}
问题存在,如果在A、B线程的基础上,再加上C、D线程
- 如果有两个增加操作同时进入,会出现虚假唤醒问题
- 解决:将if 改成while
public synchronized void increment() throws InterruptedException {
// 这个会出现问题,虚假唤醒
//if (number == 0) {
// // 等待
// this.wait();
//}
while (number != 0) {
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,加1操作完成
this.notifyAll();
}
- 什么是虚假唤醒
加锁之后,其实就是“单线程”,因为只有一个线程生产、一个线程消费,如果现在有两个线程去生产,两个线程去消费,就会出现虚假唤醒的情况
– 两个消费者都在等待,一个生产者生产后,就会唤醒两个等待的消费者,直接往下执行,那么就会产生-1的情况。(因为if在前面已经执行过了)
– 解决办法就是把if换成while,那唤醒之后还需要再次判断
JUC版的生产者和消费者问题
class Data {
private int number = 0;
Lock lock1 = new ReentrantLock();
Condition condition = lock1.newCondition();
public void increment() throws InterruptedException {
lock1.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock1.unlock();
}
}
public void decrement () throws InterruptedException {
lock1.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
}
}
}
condition实现精准唤醒特定线程
- 题目:轮流打印ABC
// 轮流打印ABC===原理精准唤醒
public class C {
public static void main(String[] args) {
Cdata cdata = new Cdata();
new Thread(()->{
for (int i = 0; i < 10; i++) {
cdata.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
cdata.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
cdata.printC();
}
},"C").start();
}
}
class Cdata {
private String str = "A";
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void printA() {
lock.lock();
try {
while (!str.equals("A")) {
condition.await();
}
System.out.println(str);
str = "B";
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (!str.equals("B")) {
condition.await();
}
System.out.println(str);
str = "C";
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (!str.equals("C")) {
condition.await();
}
System.out.println(str);
str = "A";
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- 也可以利用多个condition对象来实现