前言
锁是一种同步机制,用于控制对共享资源的访问。锁的作用是确保同一时间只有一个线程可以访问共享资源,也就是说保证了线程安全。因此在并发编程中,锁是相当重要的。
一、基本概念
1.为什么需要锁?
(1)多任务环境中才需要;
(2)任务都需要对同一共享资源进行写操作;
(3)对资源的访问是互斥的;
2.Syncronized与Lock的区别
(1)syncronized是jvm层面的内置关键字,lock是java的一个接口;
(2)syncronized实现线程同步时,若线程一阻塞,线程二则一直等待,lock则不会,会自动结束线程;
(3)syncronized会自动释放锁,lock需要手动在finally里释放(unlock),syncronized无法判断是否获得锁的状态,lock可以;
(4)syncronized的锁可重入、不可中断、非公平,lock的锁可重入、可中断、公平;
(5)lock适合大量同步代码的同步问题,syncronized适合少量;
3.常见的锁
(1)sychronized:非公平、悲观、独享、互斥、可重入的重量级锁
(2)ReentrantLock:默认非公平但可实现公平的、悲观、独享、互斥、可重入、重量级锁
(3)ReentrantReadWriteLock:默认非公平但是可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁
4.缺点
jvm锁解决不了分布式环境多任务对共享资源竞争的协同操作问题。
二、模拟抢票
(1)不用锁的情况,会出现重卖问题,应避免重卖还有超卖问题
public class Main {
public static void main(String[] args) {
DiyThread diyThread = new DiyThread();
Thread t1 = new Thread(diyThread,"窗口A");
Thread t2 = new Thread(diyThread,"窗口B");
Thread t3 = new Thread(diyThread,"窗口C");
Thread t4 = new Thread(diyThread,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class DiyThread implements Runnable {
// 成员变量(实例变量):随着对象的创建而存在,随着对象的回收而释放,存储在堆内存的对象中
private int count = 100;
// 静态变量(类变量):随着类的加载而存在,随着类的消失而消失,存储在方法区(共享数据区)的静态区
// private static int count = 100;
public void run() {
while (true) {
if (count > 0) {
// 不用锁的情况,会出现重卖问题
System.out.println(Thread.currentThread().getName() + " -> 售出第" + count + "张火车票");
count--;
try {
// 睡眠50毫秒
Thread.sleep(50);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " : 出现异常 -> " + e.getMessage());
}
} else {
System.out.println(Thread.currentThread().getName() + " : 票已售完");
break;
}
}
}
}
(2)使用【同步代码块】使线程串行同步,不然会出现线程不安全的问题
public class Main {
public static void main(String[] args) {
DiyThread diyThread = new DiyThread();
Thread t1 = new Thread(diyThread,"窗口A");
Thread t2 = new Thread(diyThread,"窗口B");
Thread t3 = new Thread(diyThread,"窗口C");
Thread t4 = new Thread(diyThread,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class DiyThread implements Runnable {
// 成员变量(实例变量):随着对象的创建而存在,随着对象的回收而释放,存储在堆内存的对象中
private int count = 100;
// 静态变量(类变量):随着类的加载而存在,随着类的消失而消失,存储在方法区(共享数据区)的静态区
// private static int count = 100;
public void run() {
while (true) {
synchronized(this) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + " -> 售出第" + count + "张火车票");
count--;
try {
// 睡眠50毫秒
Thread.sleep(50);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " : 出现异常 -> " + e.getMessage());
}
} else {
System.out.println(Thread.currentThread().getName() + " : 票已售完");
break;
}
}
}
}
}
(3)使用【synchronized】同步函数使线程串行同步,不然会出现线程不安全的问题
public class Main {
public static void main(String[] args) {
DiyThread diyThread = new DiyThread();
Thread t1 = new Thread(diyThread,"窗口A");
Thread t2 = new Thread(diyThread,"窗口B");
Thread t3 = new Thread(diyThread,"窗口C");
Thread t4 = new Thread(diyThread,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class DiyThread implements Runnable {
// 成员变量(实例变量):随着对象的创建而存在,随着对象的回收而释放,存储在堆内存的对象中
private int count = 100;
// 静态变量(类变量):随着类的加载而存在,随着类的消失而消失,存储在方法区(共享数据区)的静态区
// private static int count = 100;
public void run() {
sale();
}
private synchronized void sale() {
while (true) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + " -> 售出第" + count + "张火车票");
count--;
try {
// 睡眠50毫秒
Thread.sleep(50);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " : 出现异常 -> " + e.getMessage());
}
} else {
System.out.println(Thread.currentThread().getName() + " : 票已售完");
break;
}
}
}
}
(4)使用【java.util.concurrent】简称juc包的lock锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
DiyThread diyThread = new DiyThread();
Thread t1 = new Thread(diyThread,"窗口A");
Thread t2 = new Thread(diyThread,"窗口B");
Thread t3 = new Thread(diyThread,"窗口C");
Thread t4 = new Thread(diyThread,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class DiyThread implements Runnable {
// 成员变量(实例变量):随着对象的创建而存在,随着对象的回收而释放,存储在堆内存的对象中
private int count = 100;
// 静态变量(类变量):随着类的加载而存在,随着类的消失而消失,存储在方法区(共享数据区)的静态区
// private static int count = 100;
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
lock.lock(); // 加锁之后,以下的业务代码就是单线程环境运行,如4个线程竞争这把锁
try {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + " -> 售出第" + count + "张火车票");
count--;
Thread.sleep(50);
} else {
System.out.println(Thread.currentThread().getName() + " : 票已售完");
break;
}
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " : 出现异常 -> " + e.getMessage());
} finally {
lock.unlock(); // 解锁,最重要原因为避免死锁,无论正确、异常执行,都执行解锁
}
}
}
}