买票案例
1、定义一个类TicketThread实现Runnable接口,里面定义一个成员变量:private int tickets = 1002、在该类中重写run()方法实现买票
3、判断票大于0,就卖票,并告知是哪个窗口卖的
4、卖了票,总票数要减1
5、如果没了,线程终止
6、定义一个测试类
7、创建该类的对象
8、创建三个Thread类的对象,把TicketThread对象作为构造方法的参数,并给出对应的窗口名称
9、启动线程
//TicketThread类
public class TicketThread implements Runnable{
private int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
if(ticket <= 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 在卖票"+" 剩余 "+ticket张");
ticket--;
}
}
}
}
//main方法
public class TicketMain {
public static void main(String[] args) {
TicketThread tt = new TicketThread();
Thread t1 = new Thread(tt,"t1");
Thread t2 = new Thread(tt,"t2");
Thread t3 = new Thread(tt,"t3");
t1.start();
t2.start();
t3.start();
}
}
问题
1、相同票出现了多次 2、出现了负数的票原因
线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题解决
问题出现的条件1、是多线程环境
2、有共享数据
3、有多条语句操作共享数据
怎么实现
1、把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行
2、java提供了同步代码块的方式来解决
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}synchronized(任意对象):任意对象就可以看成就是一把锁
同步代码块的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的 运行效率
代码示例
public class SellTicket implements Runnable{
private int ticket = 100;
private Object obj = new Object();
@Override
public void run(){
while(true){
synchronized(obj){
if(ticket <= 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 在卖票"+" 剩余 "+ticket张");
ticket--;
}
}
}
}
}
public static void main(String[] args){
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,t1);
Thread t2 = new Thread(st,t2);
Thread t3 = new Thread(st,t3);
t1.start();
t2.start();
t3.start();
}
同步方法解决数据安全问题
格式
修饰符 synchronized 返回值类型 方法名(方法参数){方法体}
注意:
1、同步方法的锁对象是this
2、静态同步方法的锁对象是字节码文件
代码示例
public class SellTicket implements Runnable{
private int ticket = 100;
public synchronized void sell(){
@Override
public void run(){
while(true){
synchronized(SellTicket.class){
if(ticket <= 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 在卖票"+" 剩余 "+ticket张");
ticket--;
}
}
}
}
}
}
Lock锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
代码示例
public class SellTicket implements Runnable{
private ReentrantLock lock = new ReentrantLock();
private int ticket = 100;
@Override
public void run(){
while(true){
try{
lock.lock();
if(ticket <= 0){
break;
}else{
Thread.sleep();
ticket--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
}
}
注意:tryLock会去尝试获取锁对象,如果获取到,则进行,否则,去做其他事