目录
Questions And Answers
1、多线程代码通过两种方式如何实现
(一)继承Thread类
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread("线程一");
myThread1.setName("线程1");
System.out.println(Thread.currentThread().getName() + " 线程开始执行");
myThread1.start();
myThread1.start();
Thread.sleep(3000);
// TimeUnit.SECONDS.sleep(3); // 与上面的写法等价
System.out.println(Thread.currentThread().getName() + " 线程执行结束");
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
// 在run方法里面出现异常只能是使用try ……catch 进行抓取异常,而不能抛出异常
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 线程开始执行");
for (int i = 0; i < 100; i ++ )
System.out.println(i);
System.out.println(Thread.currentThread().getName() + " 线程执行结束");
}
}
(二)实现Runnable接口
import java.util.concurrent.TimeUnit;
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " 线程start");
MyThread21 myThread21 = new MyThread21();
Thread myThread1 = new Thread(myThread21);
myThread1.start();
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 线程end");
}
}
class MyThread21 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程start");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for (int i = 1; i <= 10; i ++ ) {
System.out.print(i + " ");
}
System.out.println();
System.out.println(Thread.currentThread().getName() + " 线程end");
}
}
2、为什么多线程存在安全问题,怎样解决多线程的安全问题?
现在假设有这样的场景,有多个窗口共同卖100张票,设计一个票箱类TicketBox,这个方法中有一个成员变量ticketRemainNumber表示剩余的票数,有一个有参构造方法TicketBox方法传入总票数,同时定义SellWindow的类实现Runnable接口,实现run方法,而在run方法的方法体内执行ticketRemainNumber -- ; 的操作(这个操作CPU在执行的时候分为三步,首先取ticketRemainNumber的值,然后执行 -1操作,最后再将运算后的ticketRemainNumber的值放回到ticketRemainNumber中 ),但是在执行这个线程的时候,可能CPU被其他线程所抢占,而另一个线程也执行类似的操作,但是ticketRemainNumber的值并没有在上一个线程更新,而这个线程会误以为ticketRemainNumber还是原来的值,因此在创建三个线程之后,很可能会出现多个线程卖同一张票的情况。总的来说,多线程的线程安全问题是指在多个线程同时访问共享资源时可能出现的数据竞争和不一致性问题。这些问题可能导致程序运行结果不确定或出现意外行为。
代码示例
为了结果展示方便,这里将ticketRemainNumber的初始值设置为10,实际的票数可能会更多
public class Demo3 {
public static void main(String[] args) {
TicketBox ticketBox = new TicketBox(10);
MySellWindow mySellWindow1 = new MySellWindow(ticketBox);
MySellWindow mySellWindow2 = new MySellWindow(ticketBox);
MySellWindow mySellWindow3 = new MySellWindow(ticketBox);
Thread thread1 = new Thread(mySellWindow1);
Thread thread2 = new Thread(mySellWindow2);
Thread thread3 = new Thread(mySellWindow3);
thread1.setName("线程1");
thread2.setName("线程2");
thread3.setName("线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
class TicketBox {
public int ticketRemainNumber;
public TicketBox(int ticketRemainNumber) {
this.ticketRemainNumber = ticketRemainNumber;
}
}
class MySellWindow implements Runnable {
TicketBox ticketBox;
public MySellWindow(TicketBox ticketBox) {
this.ticketBox = ticketBox;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
while (ticketBox.ticketRemainNumber > 0) {
System.out.println(Thread.currentThread().getName() + "已卖出第" + ticketBox.ticketRemainNumber -- + "张票");
}
}
}
卖票窗口流程图
运行结果
从运行结果不难看出线程二和线程三同时卖出第10张票,并且打印出的ticketRemainNumber的顺序错乱
问题解决
文字描述
此时在while循环的每次操作中使用synchronized关键字修饰使得一个线程在执行操作时,ticketRemainNumber -- ; 这个操作原子化(操作原子化是指把多个操作当作一个整体执行,不可分割),当一个线程拿到锁后(设置锁时应该确保锁是同一把锁,也就是同一个对象),其他线程执行到synchronized代码块部分会等待锁的返回并且抢占到CPU的情况下在执行
代码实现
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class Demo4 {
public static void main(String[] args) {
TicketBox1 ticketBox1 = new TicketBox1(10);
SellTicketWindow sellTicketWindow1 = new SellTicketWindow(ticketBox1);
SellTicketWindow sellTicketWindow2 = new SellTicketWindow(ticketBox1);
SellTicketWindow sellTicketWindow3 = new SellTicketWindow(ticketBox1);
Thread thread1 = new Thread(sellTicketWindow1);
Thread thread2 = new Thread(sellTicketWindow2);
Thread thread3 = new Thread(sellTicketWindow3);
thread1.setName("线程1");
thread2.setName("线程2");
thread3.setName("线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
class TicketBox1 {
public int ticketRemainNumber;
public TicketBox1(int ticketRemainNumber) {
this.ticketRemainNumber = ticketRemainNumber;
}
}
class SellTicketWindow implements Runnable {
TicketBox1 ticketBox1;
public static final Object TICKET_LOCK = new Object();
public SellTicketWindow(TicketBox1 ticketBox1) {
this.ticketBox1 = ticketBox1;
}
@Override
public void run() {
// 这种写法会导致同一个线程会把票卖光
// synchronized应该包裹在一次买票的操作,而不是在循环外面
// synchronized (TICKET_LOCK) {
// while (ticketBox1.ticketRemainNumber > 0) {
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
//
// System.out.println(Thread.currentThread().getName() +
// "=====> " + "卖出了第 " + ticketBox1.ticketRemainNumber -- + "张票");
// }
while (ticketBox1.ticketRemainNumber > 0) {
synchronized (TICKET_LOCK) {
// 判断此时票箱的票是否 <= 0,是的话退出循环,避免卖出第负数张票
if (ticketBox1.ticketRemainNumber <= 0) break;
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "=======> " + "卖出第"
+ (ticketBox1.ticketRemainNumber -- ) + "张票");
}
}
}
}