/*简单的卖票程序,实现多个窗口同时卖票*/
/*
创建现成的第二种方法:实现Runnable接口
步骤:
1,定义类实现Runnable接口。Runnable接口中只有一个抽象方法,就是run方法。
2,覆盖Runnable接口中的run方法。
将线程要运行的代码放在此run方法中。
3,通过Thread类建立线程对象。
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去运行指定对象的run方法,就必须明确该run方法所属的对象。
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
实现方式和继承方式的不同之处:(以后经常使用的是实现Runnable接口方式)
实现方式的好处:避免了单继承的局限性,一个类如果继承了其他类就不能再继承Thread类。那么如果想具有多线程的功能,就可以通过实现Runnable接口的方法来实现。
在定义线程时,建议使用实现方式。
两种方式的区别:
继承方式:继承Thread类>>>>线程代码放在Thread子类的run方法中。
实现方式:实现Runnable接口>>>>线程代码放在接口的子类的run方法中。共享数据
*/
/*
其实多线程是存在安全隐患问题的。比如说卖票的例子。
当有四个线程进行同时售票时,会出现售错票的安全隐患问题。举例说明:
当还有一张票时(票是几个线程的共享数据),如果线程1被执行,顺利通过if判断语句,但是还没出票就进入冻结状态(暂停在执行路径上),此时那张票并没有售出
线程2也会顺利通过if语句,然后也在执行路径上进入冻结状态,线程3和4也是如此。等线程1得到执行权时会将最后一张票售出,那么线程2,3,4就会售出错票,因为实际上已经没有票了。
问题的原因:当多条语句在操作同一个“线程共享数据”时,一个线程对多条语句只执行了一部分,还没有执行完,
安全隐患问题的解决办法:只能让一个线程一下子执行完“多条操作共享数据的语句”,即在执行操作共享数据的过程中不能被其他线程干扰或者参与操作。
java对于多线程的安全问题提供了专业的解决方式:就是同步代码块。
synchronized(对象) //就把这个对象当成锁
{
需要被同步的代码(直接看对共享数据进行操作的代码。将他们放在花括号里面)
}
对象如同锁,持有所得线程可以在同步中执行。
没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有锁。同步机制解决安全隐患问题的原理类似于火车上多个人去上卫生间的原理,每次只能去一个,并加锁。
同步的前提:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!特别要注意的地方,在同时使用同步函数和同步代码块的时候,同步块中的锁要使用this关键字。
1,必须要有两个或两个以上的线程;
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题;
弊端:多个线程都需要判断锁,较为消耗资源。
!!!!!!!!!!!!!!同步函数!!!!!!!!!!!!
非静态同步函数使用的锁是this。函数需要被对象调用,那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。每个线程都是一个Thread对象,
使用两个线程来卖票:
1,一个线程在同步代码块中;
2,一个线程在同步函数中;
都在执行卖票动作。
////////静态同步函数///////////////
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证发现不是this,因为静态方法中不可以定义this。
注意,静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class。该对象的类型是Class。
*/
class Tacket implements Runnable //extends Thread
{
private static int sum=200;//sum是共享数据,放在堆内存中,所有线程访问这一个共享数据。在一个线程运行完之后,共享数据要被同步修改。
Object obj=new Object();
boolean flag=true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Tacket.class) //同步代码块,存放需要被同步的代码。
{
if(sum>0) //操作共享数据
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
}
System.out.println(Thread.currentThread().getName()+"run"+sum--); //操作共享数据
}
}
}
}
else
while(true)
show();
}
public static synchronized void show()
{
if(sum>0) //操作共享数据
{
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
}
System.out.println(Thread.currentThread().getName()+"run"+sum--); //操作共享数据
}
}
}
class TacketDemo
{
public static void main(String[] args)
{
Tacket t=new Tacket(); //只创建了一个Ticket对象,所有线程共享此对象的数据,包括总票数sum
Thread t1=new Thread(t);//将Runnable接口的子类对象作为参数传递给Thread类的构造函数,目的是线程运行子类对象中的run方法。
Thread t2=new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
t.flag=false;
t2.start();
//Thread t3=new Thread(t);
//Thread t4=new Thread(t);
//t3.start();
//t4.start();
//t1.start();//出现异常,因为线程t1已经被开启,无需再被开启。
System.out.println("Hello World!");
}
}