1、Thread对象与Runnable接口
- Thread实现步骤少
- Runnable将线程 和 在线程上执行的任务解耦
- Thread的实现方式,存在单重继承的局限性
- Runnable,便于多线程数据的共享(电影院卖票)
2、场景
假设A电影院正在上映某电影,该电影有100张电影票可供出售,
现在假设有3个窗口售票。请设计程序模拟窗口售票的场景。
public class SalesDemo {
public static void main(String[] args) {
//第一种方式
// SalesWindow salesWindow1 = new SalesWindow();
// SalesWindow salesWindow2 = new SalesWindow();
// SalesWindow salesWindow3 = new SalesWindow();
//
// salesWindow1.start();
// salesWindow2.start();
// salesWindow3.start();
//第二种方式,基本可以模拟我们的售票场景(数据共享)
SalesTask salesTask = new SalesTask();
new Thread(salesTask).start();
new Thread(salesTask).start();
new Thread(salesTask).start();
}
}
//第一种实现方式
class SalesWindow extends Thread{
int tickets = 100;
//static int tickets = 100;//只有加static关键字才能实现三个线程共享100张票
@Override
public void run() {
//执行售票的动作
System.out.println(getName()+"售卖了第"+tickets+"张票");
}
}
//第二种实现方式
class SalesTask implements Runnable{
int tickets = 100;
@Override
public void run() {
//执行售票的动作
while (tickets > 0){
System.out.println(Thread.currentThread().getName()+"售卖出了第"+tickets--+"张票");
}
}
}
出现的问题:
加入售票延迟后,再次运行我们的仿真程序,此时我们就发现了问题:
- 相同的票,卖了多次(多卖)
窗口1卖出第99张票
窗口2卖出第99张票 - 卖出了不存在的票(即超卖)
窗口1卖出第1张票
窗口3卖出第0张票
即在多线程运行环境下,多个线程,"同时"访问共享数据,访问到了错误的结果,这就是所谓多线程数据安全问题
共享变量加锁–同步代码块
public class Demo3 {
public static void main(String[] args) {
//第二种方式,基本可以模拟我们的售票场景(数据共享)
SalesT1 salesTask = new SalesT1();
new Thread(salesTask,"窗口1").start();
new Thread(salesTask,"窗口2").start();
new Thread(salesTask,"窗口3").start();
}
}
//第二种实现方式
class SalesT1 implements Runnable {
int tickets = 100;
//作为锁对象的java对象
private Object lockObj = new Object();
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockObj){
if (tickets > 0){//double check,防止超卖
System.out.println(Thread.currentThread().getName() + "售卖出了第" + tickets-- + "张票");
}
}
}
}
}
--------------------------------
必须为同一个锁对象
// if (i % 2 == 1) {
// // i是奇数
// i++;
// synchronized (lockObj1) {
// if (tickets > 0) {// double check
// System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
// }
// }
// } else {
// // i是偶数
// i++;
// synchronized (lockObj1) {
// if (tickets > 0) { // double check
// System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
// }
// }
// }
共享变量加锁–方法
public class Sales {
public static void main(String[] args) {
//创建Runnable子类对象
SalesTask salesTask = new SalesTask();
//启动3个线程,模拟3个售票窗口, 实现了三个窗口,售卖共同的100张票
Thread window1 = new Thread(salesTask, "窗口1");
Thread window2 = new Thread(salesTask, "窗口2");
Thread window3 = new Thread(salesTask, "窗口3");
window1.start();
window2.start();
window3.start();
}
}
/*
定义SalesWindow表示,售票窗口
*/
class SalesTask implements Runnable {
//表示被售卖的100张票
int tickets = 100;
private Object lockObj = new Object();
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用方法
this.sale();
// 该run方法 target.run()
// 因此this指的当前对象,其实就是在三个线程中运行的,同一个SalesTask
// synchronized (this) {
// // 假设现在窗口1加锁成功
// if (tickets > 0) {
// System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
// }
// }
}
}
// 普通成员方法的锁对象就是其当前对象this, 对象名.方法
private synchronized void sale() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
}
}