package com.atguigu.Thread;
//题目要求:模拟火车站售票窗口,开启三个窗口售票,总票数为100张
class Window extends Thread{
static int ticket=100;
public void run() {
while(true) {
if(ticket>0) {
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}else {
break;
}
}
}
}
public class TestWindow {
public static void main(String[] args) {
Window w1=new Window();
Window w2=new Window();
Window w3=new Window();
w1.setName("售票窗口1");
w2.setName("售票窗口2");
w3.setName("售票窗口3");
w1.start();
w2.start();
w3.start();
}
}
要想启动一个多线程必须调用start( )方法。
1、创建多线程的方式二:通过实现的方式。(implements)
(1)、创建一个实现了Runnable接口的类
(2)、实现接口的抽象方法
(3)、创建一个Runnable接口实现类的对象
(4)、将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程
(5)、调用start()方法,启动线程并执行run()
(6)、如果想有多个线程,就用Thread类构造多个带形参的对象
2、实现的方式创建多线程:
实现的方式创建多线程代码:
package com.atguigu.Thread; //创建多线程的方式二:通过实现的方式。(implements) /* * 1、创建一个实现了Runnable接口的类 * 2、实现接口的抽象方法 * 3、创建一个Runnable接口实现类的对象 * 4、将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程 * 5、调用start()方法,启动线程并执行run() * 6、如果想有多个线程,就用Thread类构造多个带形参的对象 * */ //1、创建一个实现了Runnable接口的类 class PrintNum1 implements Runnable{ // 2、实现接口的抽象方法 public void run() { //子线程执行的代码 for(int i=0;i<=100;i++) { if(i%2==0) { System.out.println(Thread.currentThread().getName()+":"+i); } } } } public class TestThread { public static void main(String[] args) { // 3、创建一个Runnable接口实现类的对象 PrintNum1 p=new PrintNum1(); // p.start(); // p.run(); //要想启动一个多线程,必须调用start()方法 // 4、将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程 Thread t1=new Thread(p); // 5、调用start()方法,启动线程并执行run() t1.start();//启动线程,执行Thread对象生成时构造器形参的对象的run()方法。 //再创建一个线程 Thread t2=new Thread(p); t2.start(); } }Thread构造方法中传了一个实现Runnable接口的参数。
对比继承的方式 vs 实现的方式:
Gdhs 1、联系:public class Thread implements Runnable
都是2、哪个方式好?实现的方式优于继承的方式
Why? ①、避免了java单继承的局限性
②、如果多个线程要操作同一份资源(或数据),要适合使用实现的方式。
3、用实现的方式进行窗口售票:
package com.atguigu.Thread; //使用实现的方式进行售票窗口的实现 class Window1 implements Runnable{ int ticket=100; //在重写的run()方法之外。 @Override public void run() { // TODO Auto-generated method stub while(true) { if(ticket>0) { System.out.println(Thread.currentThread().getName()+"售票,票号:"+ticket--); }else { break; } } } } public class TestWindow1 { public static void main(String[] args) { Window1 w=new Window1(); Thread t1=new Thread(w);//形参w为实现Runnable接口的Window1类的对象 t1.start(); Thread t2=new Thread(w); t2.start(); Thread t3=new Thread(w); t3.start(); } }
一个程序运行无数次结果没有错,并不代表程序不存在隐患。程序运行结果有没有错与程序存不存在隐患不是一回事。
无论是用实现的方式的线程还是用继承的方式的线程;要用同步机制一定是只能有一个锁,synchronized中包含的是操作共享数据的代码块。
1、存在线程的安全问题:打印车票时,会出现重票、错票。
1、线程安全问题存在的原因?
由于一个线程在操作共享数据的过程中未执行完的情况下,另外的线程参与进来,共享数据存在了安全问题。
2、如何解决线程的安全问题?
必须让一个线程操纵共享数据完毕后,其他线程才有机会参与共享数据的操作。
3、java如何实现线程的安全,线程的同步机制
方式一:同步代码块
方式二:同步方法
小括号内可以实任何一个类的对象。
synchronized (同步监视器) {//经过synchronized处理以后此线程就是安全的
//操作共享数据的代码块
}
同步监视器是一把锁,必须保证多个对象共用同一把锁。
如上图:(1)、obj为任何一个类的对象,起到监视器的作用;(2)、当没有进入的时候,锁obj控制绿灯亮;(3)、当若t1进入的时候,锁obj给t1,t1进入后锁门,obj控制门红灯亮,让t1在内部运行;即使中途sleep( )也没事,直到全运行完后打开门锁出来,锁obj再控制门绿灯亮;(4)、再一次新的抢房间……内容同上。
2、实现的方式实现线程---同步机制:
package com.atguigu.Thread; //模拟火车站售票窗口,开启三个窗口售票,总票数为100张 /* * 存在线程的安全问题:打印车票时,会出现重票、错票。 * 1、线程安全问题存在的原因? * 由于一个线程在操作共享数据的过程中未执行完的情况下,另外的线程参与进来,共享数据存在了安全问题。 * * 2、如何解决线程的安全问题? * 必须让一个线程操纵共享数据完毕后,其他线程才有机会参与共享数据的操作。 * * 3、java如何实现线程的安全,线程的同步机制 * 方式一:同步代码块 * synchronized(同步监视器){ * //需要被同步的代码块(即为操作共享数据的代码) * } * 1、共享数据:多个线程共同操作的同一个数据(变量)。 * 2、同步监视器:由任何一个类的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。 * 要求:所有的线程必须共用同一把锁!!! * 方式二:同步方法 * */ class Window2 implements Runnable{ /*定义的变量与类对象都必须在重写的run方法外。*/ int ticket=100; //(ticket为共享数据)在重写的run()方法之外。 // Object obj=new Object(); @Override public void run() { // TODO Auto-generated method stub while(true) { // Object obj=new Object();在run内部定义类的对象是错误的 //方式一、使用synchronized(){ }来保证 线程安全。 synchronized (/*obj*/this/*也可以*/) {//obj可以是任何类的对象。 //this表示当前对象的,本题目为w if(ticket>0) { /* 不加被注释的部分运行100次结果不错,但并不代表程序不存在隐患;加了只是把错误的几率放大了。 */ try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"售票,票号:"+ticket--); } } } } } public class TestWindow2 { public static void main(String[] args) { Window2 w=new Window2(); Thread t1=new Thread(w);//形参w为实现Runnable接口的Window1类的对象 t1.start(); Thread t2=new Thread(w); t2.start(); Thread t3=new Thread(w); t3.start(); } }线程同步问题:锁必须是唯一的。在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,考虑同步的话,可能会有多个对象,慎用this来充当锁,会有错。
3、继承的方式实现线程---同步机制问题:
(1)、错误的:不能实现同步机制。
package com.atguigu.Thread; //模拟火车站售票窗口,开启三个窗口售票,总票数为100张 /* * 存在线程的安全问题:打印车票时,会出现重票、错票。 * */ class Window3 extends Thread{ static int ticket=100; public void run() { while(true) { synchronized (this) {//用同步包裹起来 /*此处使用this结果错误,因为现在是继承实现的线程,创建了很多对象,this表示w1\w2\w3;还不是一把锁。*/ if (ticket > 0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--); } else { break; } } } } } public class TestWindow3 { public static void main(String[] args) { /* 因为有三个对象,不能实现锁是唯一的。 */ Window3 w1=new Window3(); Window3 w2=new Window3(); Window3 w3=new Window3(); w1.setName("售票窗口1"); w2.setName("售票窗口2"); w3.setName("售票窗口3"); w1.start(); w2.start(); w3.start(); } }(2)、正确的:可以实现同步机制。(加了红底黄字的部分正确了)
package com.atguigu.Thread; //模拟火车站售票窗口,开启三个窗口售票,总票数为100张 /* * 存在线程的安全问题:打印车票时,会出现重票、错票。 * */ class Window3 extends Thread{ static int ticket=100; static Object obj=new Object(); public void run() { while(true) { // synchronized (this) {//用同步包裹起来 synchronized(obj) { /*此处使用this结果错误,因为现在是继承实现的线程,创建了很多对象,this表示w1\w2\w3;还不是一把锁。*/ if (ticket > 0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--); } else { break; } } } } } public class TestWindow3 { public static void main(String[] args) { Window3 w1=new Window3(); Window3 w2=new Window3(); Window3 w3=new Window3(); w1.setName("售票窗口1"); w2.setName("售票窗口2"); w3.setName("售票窗口3"); w1.start(); w2.start(); w3.start(); } }3、同步方法-不可以用于用继承的方式来实现线程的方法中。