用多线程做一个多窗口卖票系统,并为多线程的安全问题进行解决。
需求:
简单的卖票程序,多个窗口卖票。
思路:
票是一个共享数据被多个窗口所操作。
窗口是一个线程,多个窗口应该是一个多线程。
步骤:
票,是共享数据,应该被static修饰起来。
多窗口,是多线程,应该创建多个线程来操作票。
*/
class Ticket implements Runnable{
//private Object obj = new Object();
//定义票数据,因为创建的是一个对象资源,所以保证了数据的共享,不用static也可以
private int tick =10;
public void run(){
while(tick > 0){
try{
Thread.sleep(10); //经过此处线程休眠10秒
} catch(Exception e){
}
System.out.println(Thread.currentThread().getName()+"卖第"+tick--+"票");
}
}
}
class Test{
public static void main(String[] args){
/* 这时要设置线程的名字的话,就要用Thread类的方法了,因为Runnable是
父接口,而设置线程的方法是在Thread类中的,所以可以直接用Thread对象调用。
*/
Ticket t = new Ticket();
Thread t1 = new Thread (t);
Thread t2 = new Thread (t);
Thread t3 = new Thread (t);
Thread t4 = new Thread (t);
t1.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t4.setName("4号窗口");
t1.start();
t2.start();
t3.start();
t4.start();
//创建线程对象,把资源当成参数进行传递(保证了资源的唯一),调用Thread类的start()方法。
}
}
其运行结果为:
由结果可知最后几行卖出的票是0或者-1,这种现象是不应该出现的。运行界过之所以出现上述问题,是因为多线程在售票时出现了安全问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分(CPU就切换到另外的线程去),还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,让一个线程都执行完。在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。必须保证同步中只能有一个线程在运行。
为了实现这种情况,java提供了同步机制。当多个线程使用同一个共享资源时,可以讲处理共享资源的代码放置在一个代码块中,使用synchronized关键字来修饰,被称之为同步代码块,其语法格式如下:
synchronized(唯一对象){
操作共享的资源代码块
}
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
同步函数:
所谓的同步函数就是在函数的返回值前面加一个synchronized关键字就是同步函数了。
使用同步函数注意事项:
一定要明确哪个代码是需要进行同步,如果同步函数中的代码都是需要同步的,就可以使用同步函数。
疑问:
同步代码块的锁死自己定义的任意类型的对象,那么同步方法是否也存在锁?如果有,它的锁是什么?答案是肯定的,同步方法也有锁,它的锁就是当前调用这个方法的对象,也就是this指向的说对象。这样做的好处是,同步方法被所有线程所共享,方法所在的对象相对于所有的线程来说是唯一的,从而保证了锁的唯一性,当一个线程执行该方法时,其他的线程就不能进入该方法中,直到这性格线程执行完该方法为止,从而达到了线程同步的效果。
有时候需要同步的方法是静态方法,静态方法不需要创建对象就可以直接用”类名.方法名()“的方式调用。这时,如果不创建对象,那么这个静态同步方法的锁是什么?java中静态方法的锁是该方法的所在类的class对象,该对象可以直接用”类名.class“的方式获取。
死锁问题:
由于线程同步代码中可能嵌套同步,最容易导致的问题就是死锁。程序就停在那里不动了。我们作为程序员,应该尽量避免死锁的出现。
死锁代码:
class MyLock {
public static Object locka = new Object();
public static Object lockb = new Object();
}
class DeadLockTest implements Runnable {
private boolean flag;
DeadLockTest(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
while(true) {
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName() +"...if locka ");
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() +"..if lockb");
}
}
}
} else {
while (true) {
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() +"..else lockb");
synchronized(MyLock.locka) {
System.out.println(Thread.currentThread().getName() +".....else locka");
}
}
}
}
}
}
class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLockTest(true));
Thread t2 = new Thread(new DeadLockTest(false));
t1.start();
t2.start();
}
}
其结果是:
该例子中,两个线程都需要对方所占用的锁,但是都无法释放自己所拥有的锁,于是这两个线程都处于了挂起状态,从而造成了这种运行结果。
多线程通信:
线程间通信-等待唤醒机制:
wait(),notify(),notifyAll(),都使用在同步中,因为要对持有监视器(锁)的线程操作。
也就是说,等待和唤醒必须是同一个锁。所以要使用在同步中,因为只有同步才具有锁。
wait()和sleep() 的区别:wait() :释放资源,释放锁 。sleep() :释放资源,不释放锁。