一、卖票的多线程实现
需求:共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
代码实现:
/**
* @Author:kkoneone11
* @name:SellTicket1
* @Date:2023/8/26 11:32
*/
public class SellTicket1 implements Runnable{
private int tickets = 100;
@Override
public void run() {
while(true){
if(tickets < 0){
break;
}else {
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket1 st = new SellTicket1();
Thread thread1 = new Thread(st, "窗口1");
Thread thread2 = new Thread(st, "窗口2");
Thread thread3 = new Thread(st, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
可以看到这种程序写法的问题有:
-
相同的票出现了多次
-
出现了负数的票
问题产生的原因分析:这种多线程共享的是同一份数据,线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
二、解决问题的方案
要解决这个问题实际上就是让程序没有安全问题,如何实现其实就是让每次操作的时候只能有一个线程执行成功即可,那么可以实现的方案如下:
同步代码块
实现方法:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
优缺点:
-
好处:解决了多线程的数据安全问题
-
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
实例:
public class SellTicket1 implements Runnable{
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
//当线程进来的时候就会把这段代码锁起来
if(tickets <= 0){
break;
}else {
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
}
}
//到此处锁就会释放了
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket1 st = new SellTicket1();
Thread thread1 = new Thread(st, "窗口1");
Thread thread2 = new Thread(st, "窗口2");
Thread thread3 = new Thread(st, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
同步方法
实现方法:
锁住的对象是:this
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
静态同步方法
实现方法:
锁住的对象是:类名.class
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
实例:
public class SellTicket1 implements Runnable{
private static int tickets = 100;
@Override
public void run() {
while(true){
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean b = synchronizedMthod();
if(b){
break;
}
}else if("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
synchronized (SellTicket1.class){
if(tickets == 0){
break;
}else{
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
}
}
}
}
}
private static synchronized boolean synchronizedMthod(){
if(tickets == 0){
return true;
}else{
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
return false;
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket1 st = new SellTicket1();
Thread thread1 = new Thread(st, "窗口1");
Thread thread2 = new Thread(st, "窗口2");
Thread thread3 = new Thread(st, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
ReentrantLock()
如果我们想可以直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
实例:
public class SellTicket1 implements Runnable{
//票的数量
private int tickets = 100;
private Object obj = new Object();
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//synchronized (obj){//多个线程必须使用同一把锁.
try {
lock.lock();
if (tickets <= 0) {
//卖完了
break;
} else {
Thread.sleep(100);
tickets--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + tickets + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
// }
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket1 st = new SellTicket1();
Thread thread1 = new Thread(st, "窗口1");
Thread thread2 = new Thread(st, "窗口2");
Thread thread3 = new Thread(st, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}