实际案例:多窗口同时售卖电影票。
/**
* @PACKAGE_NAME: PACKAGE_NAME
* @Description:
* @Author: liangxu
* @Date: 2021/8/14 11:06 上午
* @Version: V1.0
*/
public class Ticket implements Runnable{
private int ricknum = 100;//电影票数量
@Override
public void run() {
while (true){//一直售卖 死循环
if(ricknum > 0){//判断是否有票
//有票,让线程睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前售出的票数字和线程名,票数-1
final String name= Thread.currentThread().getName();
System.out.println("线程:"+name+"销售电影票:"+ ricknum--);
}
}
}
public static void main(String[] args) {
//创建电影票对象
Ticket ticket = new Ticket();
//创建Thread对象,执行电影票售卖
new Thread(ticket,"窗口1").start();
new Thread(ticket,"窗口2").start();
new Thread(ticket,"窗口3").start();
}
/**
* 以上出现线程安全问题 售卖结果存在有些票有售卖多次的情况,而且出现了收费票为0和负数的情况
*/
}
解决以上问题的的方法 – 线程同步
/**
* 1.同步代码块
* 2.同步方法
* 3.同步锁 (比同步方法效率高)
* 4.特殊域及变量
* 5.局部变量
* 6.阻塞队列
* 7.原子变量
*/
1.同步代码块
/**
* @PACKAGE_NAME: PACKAGE_NAME
* @Description:
* @Author: liangxu
* @Date: 2021/8/14 11:06 上午
* @Version: V1.0
*/
public class Ticket implements Runnable{
private int ricknum = 100;//电影票数量
private Object obj = new Object();//创建锁对象
@Override
public void run() {
while (true){//一直售卖 死循环
synchronized (obj){ //同步代码块
if(ricknum > 0){//判断是否有票
//有票,让线程睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前售出的票数字和线程名,票数-1
final String name = Thread.currentThread().getName();
System.out.println("线程:"+name+"销售电影票:"+ ricknum--);
}
}
}
}
public static void main(String[] args) {
//创建电影票对象
Ticket ticket = new Ticket();
//创建Thread对象,执行电影票售卖
new Thread(ticket,"窗口1").start();
new Thread(ticket,"窗口2").start();
new Thread(ticket,"窗口3").start();
/**
* 以上线程安全
*/
}
}
2.同步方法
/**
* @PACKAGE_NAME: PACKAGE_NAME
* @Description:
* @Author: liangxu
* @Date: 2021/8/14 11:06 上午
* @Version: V1.0
*/
public class Ticket implements Runnable{
private int ricknum = 100;//电影票数量
@Override
public void run() {
while (true){//一直售卖 死循环
saleTicket();
}
}
//不是静态方法 锁对象synchronized(this) 或者说 为new出来的Ticket对象
//static 静态方法 锁对象synchronized(Ticket.class)
private /*static*/ synchronized void saleTicket(){
if(ricknum > 0){//判断是否有票
//有票,让线程睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前售出的票数字和线程名,票数-1
final String name = Thread.currentThread().getName();
System.out.println("线程:"+name+"销售电影票:"+ ricknum--);
}
}
public static void main(String[] args) {
//创建电影票对象
Ticket ticket = new Ticket();
//创建Thread对象,执行电影票售卖
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket,"窗口2");
Thread t3 = new Thread(ticket,"窗口3");
//启动线程 开始售卖
t1.start();
t2.start();
t3.start();
/**
* 以上线程安全
*/
}
}
同步锁 为一种比较复杂的机制
存在多种锁:读写锁、重入锁等等
下面以重入锁为例:ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @PACKAGE_NAME: PACKAGE_NAME
* @Description:
* @Author: liangxu
* @Date: 2021/8/14 11:06 上午
* @Version: V1.0
*/
public class Ticket implements Runnable{
private int ricknum = 100;//电影票数量
/**
* 重入锁 参数为是否公平锁,
* true:多个线程都公平拥有执行权(每个线程都公平的执行一段时间)
* false:非公平,独占锁。默认值 线程只要不释放,那么可以一直拥有执行权
*/
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){//一直售卖 死循环
lock.lock();//加锁
/**
* 使用lock锁机制,必须保证lock调用后,unlock一定会被调用,估我们采用try finally方式使用
* 否则就会出现死锁
*/
try {
if(ricknum > 0){//判断是否有票
//有票,让线程睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前售出的票数字和线程名,票数-1
final String name = Thread.currentThread().getName();
System.out.println("线程:"+name+"销售电影票:"+ ricknum--);
}
}finally {
lock.unlock();//释放锁
}
}
}
public static void main(String[] args) {
//创建电影票对象
Ticket ticket = new Ticket();
//创建Thread对象,执行电影票售卖
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket,"窗口2");
Thread t3 = new Thread(ticket,"窗口3");
//启动线程 开始售卖
t1.start();
t2.start();
t3.start();
/**
* 以上出现安全
* 每个窗口都公平售卖
*/
}
}
小结:
synchronized 和 Lock 区别
1.synchronized 是java内置关键字,在jvm层面,Lock是个java类。
2.synchronized 无法获取是否获取锁的状态,lock可以判断是否获取到锁
3.synchronized会自动释放锁(a线程执行完同步代码会释放锁;b线程执行过程中发生异常会释放锁),lock需要再finally中手工释放锁(unlock方法释放),否则容易造成线程死锁
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而lock锁就不一定会等待下去,如果城市获取不到锁,线程可以不用一直等待就结束了
5.synchronized的锁可重入、不可中断、非公平,而lock锁可重入、可判断、可公平(两者皆可)
6.lock锁适合大量的同步代码的同步问题,synchronized锁适合代码少量的同步问题。