51,929
社区成员




定义三个窗口的线程,然后卖共同的100张票,为了不让一直一个窗口买票,使用同步代码块
public class ticket extends Thread {
//定义起始票
static int ticket = 0;
//加上一个静态关键字,就会使这个类所有的对象共享这个数据,
@Override
public void run() {
while (true) {
//要保证锁对象是唯一的
synchronized (ticket.class) {
if (ticket < 100) {
ticket++;
System.out.println(getName() + "在买第" + ticket + "票");
} else {
break;
}
}
/*try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
}
}
}
测试类:
public class test {
public static void main(String[] args) {
//创建线程对象
ticket t1=new ticket();
ticket t2=new ticket();
ticket t3=new ticket();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t2.start();
t1.start();
t3.start();
}
}
刚开始我调换start方法的调用顺序,发现一直是一个窗口在卖票,好家伙嘛!不累嘛,我调换到第一个的窗口二,合着谁第一个调用start方法谁先卖,而且是一直卖到票卖买完!真离谱啊,
一开始我想着机缘巧合,就多运行了几次,很多次下来确实有一次两次的例外,然后我就搞不明白为什么,明明将同步代码块写在循环的里面,为什么还会出现这种一条路走到黑的情况!
运行截图:
真的就很离谱!而且我在前面,实现改变线程的优先级的时候,两个线程就是很规律的一个一个交替,你一个我一个,我明明设置一个优先级为10,一个优先级为1,虽然说有优先级只能说只能提高抢占CPU的概率,但是这种一个一个相互交替也太离谱了!
然后我就想这让线程执行完同步代码块后睡一会,看看其他线程怎么说,会不会抢cpu然后执行同步代码块,哎有意思,然后他们三个窗口就开始都买票了!
因为我刚开始学什么都不懂,就特别好奇这种现象和什么有关,有大佬的话希望可以解释一下!
你遇到的现象涉及到多线程并发执行的问题,涉及到线程调度、竞争条件以及操作系统的调度算法等概念。我会逐步解释你的疑问。
首先,你的代码中的同步块使用了类对象 ticket.class 作为锁。这意味着所有线程都在竞争同一个锁,即类对象 ticket.class。只有一个线程能够获得锁,执行同步块中的代码,而其他线程会在锁被释放之前等待。这确保了在同一时刻只有一个线程能够买票。
然而,你在测试类中创建了三个线程对象 t1、t2 和 t3,然后依次调用了它们的 start() 方法。线程的启动顺序是不确定的,取决于操作系统的线程调度算法。这就是为什么你会看到有时只有一个窗口在卖票的情况,因为可能第一个启动的线程率先获得了锁,而其他线程则在等待。
当你调换了线程的启动顺序,有时候会看到不同的结果,这是因为启动顺序的不同可能导致某个线程率先获得了锁。但仍然会存在不确定性。
关于线程的优先级,你提到的情况也是正常的。虽然你设置了线程的优先级,但线程调度的具体实现在不同的操作系统和Java虚拟机中可能会有所不同。优先级只是一个指示,不一定会严格按照优先级的顺序执行。
关于让线程执行完同步代码块后睡一会儿的情况,这实际上会让其他线程有机会竞争获得锁并执行同步代码块。在你的实验中,这是有效的一种方式来演示多个窗口一起卖票。
总的来说,多线程编程涉及到许多复杂的问题,包括竞争条件、线程调度、锁机制等。为了更好地控制线程执行的顺序和并发情况,你可以尝试使用更高级的并发工具,如 java.util.concurrent 包中提供的类,来实现更精细的线程协作和同步。
在你的代码中,虽然将同步代码块写在了循环内部,但是问题出在了你创建了多个独立的ticket对象。每个对象都有自己的锁,因此每个线程在执行同步代码块时,只会锁住自己对象的锁,而不会锁住其他对象的锁。
因此,虽然你在run方法中使用了同步代码块来确保每次只有一个线程进入临界区,但是由于每个线程都在独立的对象上执行,所以锁住的是不同的对象锁,导致多个线程可以同时进入临界区。
要解决这个问题,你需要将票数变量定义为静态变量,这样所有的ticket对象都可以共享同一个变量。同时,在同步代码块中,使用一个共同的锁对象来确保只有一个线程可以进入临界区。
还有一种简单的方式把你的同步锁中的ticket.class对象直接换成一个字符串,字符串因为在堆得常量池里面储存,所以引用的对象地址是同一个地址,这样就保证了你的锁对象唯一,不会 你new 一下就出现一个新的锁对象,要么你就给你的锁对象加个static保证他静态共享也行!理解了吗?