Java 多线程 (Thread 类)

时间:2023-03-09 16:48:02
Java 多线程 (Thread 类)

 1.多线程  2.卖票  


1.多线程实现

两种方式可以实现多线程:

  • 继承 Thread 类,重写 run 方法;定义对象,调用 start 方法
  • 创建类实现 Runnable 接口,作为实参传递给 thread 的构造方法。定义对象,调用 start 方法。

1.1.继承 Thread

  • 继承类,重写方法
class TDemo1 extends Thread {
public String name; // 取个名字,便于识别 public TDemo1 (String name) { // 构造方法
this.name = name;
} @Override
public void run() { // 重写 run 方法
show();
} public void show() {
System.out.println(name + ": talk show time");
}
}
  • 创建对象,调用 start 方法启动线程
TDemo1 td1 = new TDemo1("梁宏达");
TDemo1 td2 = new TDemo1("李晨伟");
TDemo1 td3 = new TDemo1("窦文涛");
TDemo1 td4 = new TDemo1("备胎说车"); td1.start();
td2.start();
td3.start();
td4.start();

1.2.实现 Runnable

  • 实现接口
class TDemo2 implements Runnable {
public String name; // 识别 public TDemo2(String name) { // 构造方法
this.name = name;
} @Override
public void run() { // 实现方法
show();
} private void show() {
System.out.println(name + " 新媒体开播");
} }
  • 创建对象,调用方法 start
Thread td1 = new Thread(new TDemo2("备胎说车"));
Thread td2 = new Thread(new TDemo2("30秒懂车"));
Thread td3 = new Thread(new TDemo2("汽车洋葱圈"));
Thread td4 = new Thread(new TDemo2("根叔说车")); td1.start();
td2.start();
td3.start();
td4.start();

2.卖票

2.1.多线程的弊端

  • 实现类
public class Piao implements Runnable {
public static int ticketCount = 5; // 总票数
public int tmpCount = 0; // 售票数量
public String tNum; // 窗口编号、查询窗口radio public Piao(String tNum) { // 构造方法
this.tNum = tNum;
} @Override // 方法重写
public void run() {
if (!"radio".equals(this.tNum)){
sell();
} else {
try {
radio();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} // 查询台,只查询剩余票数,不卖票
private void radio() throws InterruptedException {
int n =0;
while (true) {
System.out.println("剩余票数: " + ticketCount);
Thread.sleep(100);
if(n>3) {
break;
}
n++;
}
} // 售票窗口
private void sell() {
while (true) { // 窗口不打烊
// 车票数量统一管理
if (ticketCount > 0) {
System.out.println("售票窗口 " + tNum + "销售车票" + (++tmpCount) + "张");
ticketCount--;
}
}
}
}
  • 主方法
public class TicketDemo1 {

    public static void main(String[] args) {

        Thread t1 = new Thread(new Piao("001"));
Thread t2 = new Thread(new Piao("002"));
Thread t3 = new Thread(new Piao("003"));
Thread t4 = new Thread(new Piao("004")); // 售票窗口
Thread t0 = new Thread(new Piao("radio")); // 查询窗口 t1.start();
t2.start();
t3.start();
t4.start();
t0.start();
}
}
  • 运行结果:每次运行都不尽相同,出问题的时候多有发生。
售票窗口 003销售车票1张
售票窗口 003销售车票2张
售票窗口 003销售车票3张
售票窗口 003销售车票4张
售票窗口 002销售车票1张
售票窗口 001销售车票1张
售票窗口 004销售车票1张
售票窗口 003销售车票5张
剩余票数: 2
剩余票数: -3
剩余票数: -3
剩余票数: -3
剩余票数: -3

同时开通了 四 个售票窗口,一 个查询窗口,四 个售票窗口一共销售了 八 张票。而实际总票数为 五 张。程序运行出错了

2.2.多线程

  • 出现问题的原因:
    • 多线程同时运行
    • 有些数据被多线程共享
    • 线程会修改共享的数据
  • 解决的方法:在共享数据的读取处理,加锁
  • 加锁须被线程共享

2.3.多线程加锁

  • 加锁(对象锁):使用同步代码块,须事先定义一个 静态锁对象。 使用了 “同一个锁对象圈定的代码块” 的线程,共享锁。
public class Piao2 implements Runnable {
public static int ticketCount = 50; // 总票数
static Object obj = new Object(); // 锁对象 // 多线程重写方法
@Override
public void run() {
sell();
} // 售票员售票方法
private void sell() {
while (true) { // 售票窗口不打烊
synchronized (obj) { // 锁子放在循环内
// 车票数量统一管理
if (ticketCount > 0) {
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
} ticketCount--;
System.out.println(Thread.currentThread().getName() + " 窗口,剩余车票" + ticketCount + "张。");
}
}
}
}
}

运行后,发现偶尔还是会多卖。说明还是没有锁住。修改 锁对象定义,多个线程不能共享锁。把锁定义为 static。

    static Object obj = new Object(); // 锁对象
  • 加锁(方法锁):为一个静态方法加锁,使得调用方法的对象亦可 共享锁。
public class Piao3 implements Runnable {
public static int ticketCount = 100; // 总票数 // 多线程重写方法
@Override
public void run() {
try {
sell();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 售票员售票方法
private static synchronized void sell() throws InterruptedException {
while (true) { // 窗口不打烊
if (ticketCount > 0) {
System.out.println(Thread.currentThread().getName() + "售票窗口" + "查询 -> 剩余票数" + ticketCount );
ticketCount--;
}
}
}
}

  加锁后,确实没有 出现多卖票的情况。但是有个马达,就是只要哪个窗口卖出了第一张票,就一卖到底,其他窗口没有票卖。

  • 原因是:俩原因
    • 静态方法,静态方法加锁所有对象共享该锁
    • 加锁后,进入循环,谁调用方法早,当然就把票往空了卖(比黄牛还黄)。
  • 解决一个窗口把票往光了卖的情况,分析:去掉静态,不可行,因为去掉静态后加锁失去效果。每个对象自己加自己的锁……失去加锁意义,

  调整:继续使用静态锁。把 synchronized 限定放到循环里边,每一次循环都加锁,每次循环都同时加锁、解锁。解锁后就可以继续竞争。

// 售票员售票方法
private void sell() {
while (true) { // 窗口不打烊
sell(1);
}
} private static synchronized void sell(int n) {
if (ticketCount > 0) {
System.out.println(Thread.currentThread().getName() + "售票窗口" + "查询 -> 剩余票数" + ticketCount);
ticketCount--;
}
}

静态方法加锁,保证了多个对象共享一个锁。锁放在循环外,保证每个窗口都有票可卖。

2.4.锁对象

  在 2.3 中,对象锁有一个明确定义的静态对象,而方法锁中只能看到方法须是静态的。非静态方法,锁对象是 this;静态方法的锁对象是该类的字节码对象。由于字节码对象,整个类、类对象共享,因此锁能限定线程;而使用了非静态方法,就无法锁定。