以下只是认识线程的开始,以static表示几个线程控制同一个变量增减的情形开始逐步深入多线程依次
特别注意;
Thread类才是和线程相关的,而Runnable接口本身和线程无直接关系
Thread类本身实现了Runnable接口
Runnable 接口中仅仅定义了一个抽象方法run
Runnable接口本身就是定义了一个代码存放的位置(需要执行的方法存放在run方法中)
Thread类实现Runnable接口作用只是为线程中运行的代码确定了一个存放位置,换句话说:
线程Thread对象相当于一辆汽车,Runnable相当于一个可以存放物品的形状大小等可随机变化的箱子,里面可以盛装货物,
而目标运行的代码(我们存放在run方法中的代码)相当于要运行的货物,想要使用汽车(Thread)运行货物(代码)到某个地方,必须将货物存放在指定的箱子(Runnable接口的run()方法)中.
特别注意本实例运行的结果分析:
1、因为是多线程,所以出票的结果数目不是完全从100依次降到1;
2、最后的结果中,并非1打印到最后,是因为电脑为多核CPU,处理时并非完全理论中的依次降低的结果
因为Windows一边调用Java虚拟机执行票数减少1的程序,一边调用控制台,打印出票结果,在此显示的结果故而如图
package practiceMySelf;
/**
* 需求:简单的卖票程序
* 多个窗口同时卖
* */
public class Ticket extends Thread{
//用tick变量赋值100,表示存储100张票
//private int tick=100;
//static 用于控制以下程序中,几个线程共同控制一个变量的增减
//因为是几个窗口共同卖100张票
private static int tick=100;
public void run(){
while(true){
if(tick>0){
//票每卖出一张减少1
System.out.println(Thread.currentThread().getName()+"..sales:"+tick--);
}
}
}
}
class ThreadDemo1{
public static void main(String[] args){
//创建4个线程,表示四个同时开卖的窗口
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
//开始售票
t1.start();
t2.start();
t3.start();
t4.start();
}
}
package practiceMySelf;
/**
* 需求:简单的卖票程序
* 多个窗口同时卖
*
* 创建线程的第二种方式,实现Runable接口
* 步骤:
* 1、定义类实现Runnable接口
* 2、覆盖Runnable接口中的run方法
* 将线程要运行的代码存放在该run方法中。
* 3、通过Thread类建立线程对象
* 4、将Runnable接口的子类对象作为实际参数传递给Thread的构造方法
* 为什么要将Runnable接口的子类对象传递给Thread的构造函数?
* 因为,自定定义的run方法所属的对象时Runnable接口口的子类
* 所以要让线程去指定指定对象的run方法
*5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
* */
public class Ticket implements Runnable{//extends Thread{
//用tick变量赋值100,表示存储100张票
//private int tick=100;
//static 用于控制以下程序中,几个线程共同控制一个变量的增减
//因为是几个窗口共同卖100张票
private static int tick=100;
public void run(){
while(true){
if(tick>0){
//票每卖出一张减少1
System.out.println(Thread.currentThread().getName()+"..sales:"+tick--);
}
}
}
}
class ThreadDemo1{
public static void main(String[] args){
// //创建4个线程,表示四个同时开卖的窗口
// Ticket t1=new Ticket();
// Ticket t2=new Ticket();
// Ticket t3=new Ticket();
// Ticket t4=new Ticket();
// //开始售票
// t1.start();
// t2.start();
// t3.start();
// t4.start();
Ticket t=new Ticket();
//创建4个线程,表示四个同时开卖的窗口
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//开始售票
t1.start();
t2.start();
t3.start();
t4.start();
}
}
以上代码中可发现:
创建线程的方式有两种,继承Thread类和实现Runnable接口两种,有何区别呢?
实现方式好处:避免了继承的局限性,Java中继承为单一继承机制
在定义(创建)线程时,建议使用实现的方式
两种方式区别:
继承Thread:线程代码存放在Thread子类的run方法中
实现Runnable:线程代码存放在接口的子类的run方法中
线程安全问题 ——线程共享数据不合理问题
实例情况如下代码,运行结果可能会出现票数为0的情况(可能在多次试验过程中也未出现非法数据如0 -1等问题,但一定存在可能性)
package practiceMySelf;
class Ticket1 implements Runnable{//extends Thread{
//用tick变量赋值100,表示存储100张票
//private int tick=100;
//static 用于控制以下程序中,几个线程共同控制一个变量的增减
//因为是几个窗口共同卖100张票
private int tick=100;
public void run(){
while(true){
if(tick>0){
//以下让线程睡眠,就是为了模拟线程不安全情形的状态
try {Thread.sleep(10);}
catch (InterruptedException e) {e.printStackTrace();}
//票每卖出一张减少1
System.out.println(Thread.currentThread().getName()+"..sales:"+tick--);
}
}
}
}
class ThreadDem{
public static void main(String[] args){
// //创建4个线程,表示四个同时开卖的窗口
// Ticket t1=new Ticket();
// Ticket t2=new Ticket();
// Ticket t3=new Ticket();
// Ticket t4=new Ticket();
// //开始售票
// t1.start();
// t2.start();
// t3.start();
// t4.start();
Ticket1 t=new Ticket1();
//创建4个线程,表示四个同时开卖的窗口
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//开始售票
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/**
* 需求:简单的卖票程序
* 经过运行发现,可能会出现打印出0 -1 -2号票的情形
*多线程的运行出现了安全问题
*问题的原因:
* 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
* 执行完,另一个线程参与进来执行,导致共享数据的错误
*
* 解决办法:
* 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程
* 不可以参与执行;
*
* Java对于多线程的安全问题提供了专业的解决方式:
* 就是同步代码块
* synchronized(对象){
* 需要被同步的代码
* }
*
* */
package practiceMySelf;
class TicektSecrety {
}
package practiceMySelf;
/**
* 需求:简单的卖票程序
* 经过运行发现,可能会出现打印出0 -1 -2号票的情形
*多线程的运行出现了安全问题
*问题的原因:
* 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
* 执行完,另一个线程参与进来执行,导致共享数据的错误
*
* 解决办法:
* 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程
* 不可以参与执行;
*
* Java对于多线程的安全问题提供了专业的解决方式:
* 就是同步代码块
* synchronized(对象){
* 需要被同步的代码
* }
* 对象如同锁,持有锁的线程可以在同步中执行
* 没有持有锁的线程及时获取CPU的执行权,也进不去,因为没有获取锁
*
* 火车上的卫生间--经典举例:
* 同步现象如同火车上同一节列车上抢该列车上的卫生间
*
* 同步的前提:
* 1、必须要有两个或者两个以上的线程
* 2、必须是多个线程使用同一个锁
* 必须保证同步中只能有一个线程在运行
*
* 同步的好处:解决了多个线程的安全问题
* 弊端:多个线程需要判断锁,较为消耗资源
* 越安全越麻烦
*
* */
//以下person类是在问题分析过程中产生的垃圾,体现了思路一步步来的经过,用于帮助自身后期便于理解用
// class Person{//
// public Person(){super();}
// }
class Ticket1 implements Runnable{//extends Thread{
//用tick变量赋值100,表示存储100张票
//private int tick=100;
//static 用于控制以下程序中,几个线程共同控制一个变量的增减
//因为是几个窗口共同卖100张票
private int tick=100;
//Person p=new Person();
Object obj=new Object();
public void run(){
while(true){
//synchronized(p){
synchronized(obj){
if(tick>0){
try {Thread.sleep(10);}
catch (InterruptedException e) {e.printStackTrace();}
//票每卖出一张减少1
System.out.println(Thread.currentThread().getName()+"..sales:"+tick--);
}
}
}
}
}
class ThreadDem{
public static void main(String[] args){
// //创建4个线程,表示四个同时开卖的窗口
// Ticket t1=new Ticket();
// Ticket t2=new Ticket();
// Ticket t3=new Ticket();
// Ticket t4=new Ticket();
// //开始售票
// t1.start();
// t2.start();
// t3.start();
// t4.start();
Ticket1 t=new Ticket1();
//创建4个线程,表示四个同时开卖的窗口
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//开始售票
t1.start();
t2.start();
t3.start();
t4.start();
}
}