买火车票问题
package com.lanou4g;
public class Demo01 {
public static void main(String[] args) {
TicketSale ticketSale = new TicketSale();
Thread t1 = new Thread(ticketSale,"网上购票");
Thread t2 = new Thread(ticketSale,"黄牛购票");
Thread t3 = new Thread(ticketSale,"实窗购票");
t1.start();
t2.start();
t3.start();
}
}
class TicketSale implements Runnable{
private int ticket =100;
@Override
public void run() {
while (true) {
if (ticket>0) {
System.out.println(Thread.currentThread().getName()+"------"+ticket);
ticket--;
}else {
break;
}
}
Thread.yield();
}
}
从打印结果我们可以看出有两个问题:出现负数的情况 一张票卖几次;
很显然这时不符合要求的:
原因:
1.CPU是随机执行线程的也就是说线程可以在方法的任何位置停止执行(利用极限位置去假设) 当一线程进入run方法 但是还未打印就被CPU抛弃处于就绪状态 ,另外的线程也进入run方法,那么就会出现一票多卖的情况.
2.当卖到最后一张票时 任在打印前被CPU抛弃,处于就绪状态,那么另外的线程任然可以通过判断进入run方法,没条线程结束都要进行递减,所以导致问题二出现;
解决办法:
根据上面出现的问题分析 :只要保证同一时间 只有一个线程操作共享数据就行;(当一个线程操作数据时 其他线程 不能操作,只能等着操作的线程 操作完毕以后 才能继续进来操作数据);
于是引入同步锁的概念;
同步代码块(同步锁)
锁 任意的对象,但是保证只有一把锁(锁是唯一的 使用的同一把锁)
synchronized(锁){
操作共享数据的代码
}
同步代码块是如何做到 一个线程执行代码时,其他线程在外面等着?
同步代码块规程:
当线程进入同步代码块时 先看一下有没有锁
如果有锁就进入同步代码块中执行代码 ,在进去的同时会获取这把锁 带着这把锁执行代码
当代码执行完毕,出同步代码块时 将这把锁释放(还回去)
如果没锁 :线程跟在同步代码块前等待(等着有锁猜能进)
public class Demo01 {
public static void main(String[] args) {
TicketRunable ticketRunable = new TicketRunable();
Thread t1 = new Thread(ticketRunable);
Thread t2 = new Thread(ticketRunable);
Thread t3 = new Thread(ticketRunable);
t1.start();
t2.start();
t3.start();
}
}
/*class MyThread1 extends Thread{
//声明票
private int ticket = 50;
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
}
}*/
// 接口实现
class TicketRunable implements Runnable{
//声明票
private int ticket = 50;
//声明锁对象(保证只有一把锁)
private Object oracle = new Object();
//卖票
@Override
public void run() {
//循环出票
while (true) {
synchronized (oracle) {
if (ticket>0) {
//为了让测试结果更明显 加个线程休眠
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//卖票
System.out.println(Thread.currentThread().getName()+"*****"+ticket);
//卖一张少一张.
ticket--;
}else {
//卖光了 结束循环
break;
}
}
//让出CPU的执行资源(不一定让)
Thread.yield();
}
}
}
同步方法:作用和同步代码块相同,并且也使用synchronized 关键字
该关键字 声明在方法上.
作用: 同一时间只能有一个线程 进入到同步方法中 执行代码
方法中也是 操作共享数据的代码
同样 有锁 处理方式和同步代码块一样.
同步的成员(对象)方法 锁是this(本类的对象)
静态方法: 也同样可以加锁 锁是 类锁 类名.class (表示这个类)
静态方法中不能使用this(方法中不一定有对象)
public class Demo02 {
public static void main(String[] args) {
TicketRunable1 ticketRunable1 = new TicketRunable1();
Thread t1 = new Thread(ticketRunable1);
Thread t2 = new Thread(ticketRunable1);
Thread t3 = new Thread(ticketRunable1);
t1.start();
t2.start();
t3.start();
}
}
// 接口实现
class TicketRunable1 implements Runnable{
//声明票
private static int ticket = 50;
//卖票
@Override
public void run() {
//循环出票
while (true) {
if (fun()) {
break;
}
//让出CPU的执行资源(不一定让)
Thread.yield();
}
}
private static synchronized boolean fun(){
if (ticket>0) {
//为了让测试结果更明显 加个线程休眠
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//卖票
System.out.println(Thread.currentThread().getName()+"*****"+ticket);
//卖一张少一张.
ticket--;
return false;
}else {
return true;
}
}
}
JDK1.5 出现的Lock 类
使用实现类 ReentrantLock
Lock() 获取锁
unLock 释放锁 保证锁一定会被释放
使用前提: 和同步代码块一样 要保证 用的是同一把锁
使用格式:
try{
操作共享数据的代码
}finally{
释放锁
}
public class Demo03 {
public static void main(String[] args) {
TicketRunable2 ticketRunable2 = new TicketRunable2();
Thread t1 = new Thread(ticketRunable2);
Thread t2 = new Thread(ticketRunable2);
Thread t3 = new Thread(ticketRunable2);
t1.start();
t2.start();
t3.start();
}
}
class TicketRunable2 implements Runnable {
private int ticket = 50;
// 声明锁 保证是同一把
private ReentrantLock Lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 获取锁
Lock.lock();
try {
if (ticket > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "*****" + ticket);
ticket--;
} else {
break;
}
Thread.yield();
} finally {
Lock.unlock();
}
}
}
}
简单应用:
package com.lanou3g;
/*
公司年会 进入公司有两个门(前门和后门)
进门的时候 每位人都能获取一张彩票(7位数)
公司有100个员工
利用多线程模拟进门过程
统计每个入口入场的人数
每个人拿到的彩票的号码 要求7位数字 不能重复
打印格式:
编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是: [17, 24, 29, 30, 31, 32, 07]
编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是: [06, 11, 14, 22, 29, 32, 15]
…..
从后门 入场的员工总共: 45 位员工
从前门 入场的员工总共: 55 位员工
保证总人数100即可
*/
import java.util.ArrayList;
public class Demo04 {
public static void main(String[] args) {
//创建测试线程
GSRunnable runnable = new GSRunnable();
//前门
Thread t1 = new Thread(runnable,"前门");
//后门
Thread t2 = new Thread(runnable,"后门");
//启动线程
t1.start();
t2.start();
}
}
class GSRunnable implements Runnable{
//声明共享数据
private int pNum = 100;
//记录前门进的人数
private int qNum = 0;
//记录后门进的人数
private int hNum = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
//结束循环的条件
if (pNum <= 0) {
break;
}
//判断进来的线程是前门还是后门
String name = Thread.currentThread().getName();
if (name.equals("前门")) {
qNum++;
System.out.println("编号为:" + (100 - pNum + 1) + "的员工从前门入场,拿到的彩票是" + getLottery());
pNum--;
}else {
hNum++;
System.out.println("编号为:" + (100 - pNum + 1) + "的员工从后门入场,拿到的彩票是" + getLottery());
pNum--;
}
//分别打印前后门员工人数
if (pNum ==0) {
System.out.println("从前门 入场的员工总共:"+qNum+" 位员工");
System.out.println("从后门 入场的员工总共:"+hNum+" 位员工");
}
}
//让出CPU资源
Thread.yield();
}
}
//打印彩票号
public ArrayList<Integer> getLottery() {
ArrayList<Integer> list = new ArrayList<>();
//随机七个数 放入集合中
//0-100
while(list.size() < 7) {
//随机数
int random =(int)(Math.random()*(99 - 0 + 1) + 0 );
//判断不存在就添加进集合
if (!list.contains(random)) {
list.add(random);
}
}
//返回结果
return list;
}
}
线程死锁:
package com.lanou3g;/* * 线程死锁 * 前提: 1.必须要有同步锁的嵌套 * 2.锁对象唯一(使用同一把