Java多线程学习笔记02

Java多线程学习笔记01:https://blog.csdn.net/weixin_44211980/article/details/106125754

3. 线程同步

3.1 案例引入

【卖票问题】
需求:某电影院目前正在上映国产大片,共有100张票,有3个窗口卖票,请设计一个程序模拟该电影院卖票

【实现思路】

  1. 定义一个SellTicket类实现Runnable类,定义一个成员变量tickets表示总票数
  2. 在SellTicket类中重写run()方法实现卖票

代码如下:

public class SellTicket implements Runnable {
	private int tickets = 100;
	private void sellticket() {
		if(tickets > 0) {
			System.out.println(Thread.currentThread().getName() + "正在售出第" + tickets + "张票");
			--tickets;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
	}
	@Override
	public void run() {
		while(tickets > 0) {
			sellticket();
		}
	}
}

public class ThreadStudy {
	public static void selltickets() {
		SellTicket st = new SellTicket();
		Thread t1 = new Thread(st, "A");
		Thread t2 = new Thread(st, "B");
		Thread t3 = new Thread(st, "C");
		//设置ABC三个窗口同时卖票
		t1.start();
		t2.start();
		t3.start();
	}
	public static void main(String[] args) {
		selltickets();
	}
}

运行部分结果如下:

C正在售出第100张票
B正在售出第100张票
A正在售出第100张票
A正在售出第97张票
C正在售出第97张票
B正在售出第97张票
A正在售出第94张票
C正在售出第94张票
B正在售出第94张票
C正在售出第91张票
B正在售出第91张票
A正在售出第91张票
A正在售出第88张票
C正在售出第88张票
B正在售出第88张票
A正在售出第85张票
B正在售出第85张票
C正在售出第85张票
...

可以看到运行结果中同一张票被卖出了多次

【问题分析】
由于ABC三个进程是同时运行,那么当A进程运行到ticket–之前,B、C进程有可能会运行到输出一栏,此时你的ticket还没有改变,那么就会出现相同的票被卖出多次的情况,于是引入了线程数据安全问题

【卖票问题数据安全问题的解决】
多线程是否有数据安全问题的标准

  1. 是否是多线程环境
  2. 是否有共享数据
  3. 是否有多条语句操作共享数据

只要破坏了以上问题中的一个,那么数据安全问题就会被解决,由于此案例中多线程环境共享数据必须被满足,那么我们尝试解决多条语句操作共享数据的问题

于是我们又引入了的概念,即将多条语句共享数据的代码块起来,让任意时刻只有一个线程执行即可

3.2 同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized(object) {

}

对于卖票问题,代码改进如下:

package StudyDemo02;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {
	private int tickets = 100;
	private void sellticket() {
		if(tickets > 0) {
			System.out.println(Thread.currentThread().getName() + "正在售出第" + tickets + "张票");
			--tickets;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
	}
	public void lock() {
		synchronized(this) {
			sellticket();
		}
	}
	@Override
	public void run() {
		while(tickets > 0) {
			lock();
			sellticket();
		}
	}
}

运行结果如下:

A正在售出第100张票
A正在售出第99张票
A正在售出第98张票
C正在售出第97张票
C正在售出第96张票
C正在售出第95张票
C正在售出第94张票
C正在售出第93张票
C正在售出第92张票
B正在售出第91张票
B正在售出第90张票
B正在售出第89张票
B正在售出第88张票
B正在售出第87张票
...

3.3 线程安全的类

  1. StringBuffer
    线程安全,可变字符序列
    从JDK5开始被StringBuilder代替,通常应该使用StringBuilder类
  2. Vector
    该类改进了List接口,使其成为Java.Collections.Framework的成员,如果不需要线程安全的实现,建议使用ArrayList代替Vector
  3. Hashtable
    该类实现了一个Hash表,他将键映射到值
    如果不需要线程安全的实现,建议使用HashMap代替

3.4 Lock锁

虽然我我们呢可以理解代码同步块和同步方法的对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清楚的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock中常用函数
void lock(); 获得锁
void unlock(); 释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

还是卖票问题,使用Lock的代码如下:

package StudyDemo02;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {
	private int tickets = 100;
	private Lock lock = new ReentrantLock();
	private void sellticket() {
		if(tickets > 0) {
			System.out.println(Thread.currentThread().getName() + "正在售出第" + tickets + "张票");
			--tickets;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
	} lock03() {
		try {
			lock.lock();
			sellticket();
		} finally { //为防止在锁的过程中出现问题导致无法解锁,在这里加上一个finally关键字表示无论如何也要解锁
			lock.unlock();
		}
	}
	@Override
	public void run() {
		while(tickets > 0) {
			lock03();
		}
	}
}

Java多线程学习笔记03:https://blog.csdn.net/weixin_44211980/article/details/106146745

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值