JAVA多线程(一)

最近发现自己对JAVA多线程这一块的了解可以说是完全为0,于是开始对多线程基础进行了一些学习,今天主要来说一说JAVA多线程的一些基本用法吧

说到线程,那么我们得先知道线程是什么,为什么要用多线程,而提到线程,我们就还得了解另外两个与其密切相关的名词,程序与进程

作为一名程序员,那么对程序肯定不陌生,官方的解释是,程序是为了完成完成某项特定的任务,使用某种语言,编写的一组指令的集合。说白了程序也就是人想让机器做一件事,使用机器能读懂的语言,也就是计算机语言,机器看懂之后进行执行。

那什么是进程呢,我们在打开任务管理器时,应该会看到这个字眼,进程指的就是正在进行的程序

而线程,指的是在一个进程中,执行的一套功能流程,称为线程,类似的,在一个进程中执行的多套功能流程就叫做多线程

比如我们使用某某杀毒软件,打开其图形界面,这就是一个进程,而该软件有很多功能,比如杀毒、体检等等,不同的功能同时执行就是多线程

接下来我们看看JAVA的多线程吧,JAVA采用的是抢占式策略系统,指的是系统会分配给每个执行任务的线程很小的时间段,当该时间段用完后,系统会自动地剥夺其CPU使用权,交给其他线程执行。这样可以保证每个线程都能被使用到。而JVM本身就是多线程的,我们常用的main方法,就是一个线程,叫做主线程。

JAVA中提供了两种执行线程的方式,我们先看看第一种
首先我们声明一个类继承Thread类:
在这里插入图片描述
接着重写run()方法,在方法体中放入我们想要实现的功能,比如我想打印1到100之间的偶数,为了方便观察,我们使用Thread类中的getName()方法获取到当前的线程名:

/线程执行体
    public void run(){

        for (int i=0;i<100;i++){
            if (i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }

    }

这样我们就新建了一个线程,接下来我们创建该线程的实例:

PrimeThread primeClass=new PrimeThread();

再调用start()方法启动线程,默认会调用run()方法:

primeClass.start();

为了体现多线程,我们将该线程放在main方法中执行,因为main方法本身也是一个线程,同时我们再创建一个新的线程,为了区分,我们在main方法里编写一个循环,输出100至200之间的偶数,最终代码是这样:

 public static void main(String[] args) {
        PrimeThread primeClass=new PrimeThread();
        primeClass.start();

        PrimeThread primeClass2=new PrimeThread();
        primeClass2.start();

        for (int i=100;i<200;i++){
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }

最终结果:
在这里插入图片描述
可以看到三个线程在交替运行自己的功能,这样一个简单的多线程就完成了,在这里有几个线程中常用的方法:
currentThread():获取当前线程

getName():获取线程名字

start():启动线程

刚才我们有提到,调用start()方法默认会调用run()方法,那么run与start方法究竟有何不同,我们将start改为run再执行,通过运行结果看看两者的区别:
在这里插入图片描述
多次运行后我们发现,调用run方法后,两个子线程不再执行,而是主线程在完成两个子线程的功能,这说明当前只有主线程启动了而两个子线程并没有启动,这也就是说明,当我们要启动一个线程时,要调用start方法。

接下来我们说一下创建执行线程的第二种方式:
首先我们声明一个类,实现runnable接口:
在这里插入图片描述
同样,我们实现第一种方式相同的功能,首先实现run()方法,再写入循环输出语句:

@Override
    public void run() {
        for (int i=0;i<100;i++){
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i++);
            }
        }

接下来创建该类的实例:

 HellloThread hellloThread=new HellloThread();

再创建Thread类的实例,将我们上面实现类的实例以参数的形式传入:

Thread t1=new Thread(hellloThread);

最后调用实例的start方法启动线程,最终代码:

 HellloThread hellloThread=new HellloThread();
      Thread t1=new Thread(hellloThread);
      t1.start();
        for (int i=100;i<200;i++){
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }

而由于Runnable接口是一个函数式接口,如果了解过Lambda表达式,我们也可以使用该表达式简化代码:

 Runnable runnable1=()->{
            for (int i=0;i<100;i++){
                if (i%2==0){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            };
        };
        Thread t1=new Thread(runnable1);
        t1.start();
        for (int i=100;i<200;i++){
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }

无需新建Runable接口的实现类以及重写run方法,直接使用Lambda表达式即可解决,但它也有局限性,一是lambda表达式只对函数式接口适用,二是表达式中的参数只能是final变量,比如我们要在线程中执行数字的运算就有点麻烦了

至此,两种创建启动线程的方法就写完了,那么这两种方法究竟有什么区别?我们通过一个功能进行比较,假设我们现在模拟火车站的购票窗口,100张票开三个窗口同时卖出。我们先使用继承Thread类的方式:
首先新建一个Window类继承Thread类:
在这里插入图片描述
声明一个全局变量100表示票数:

int tick=100;

重写run方法,方法体中放入售票方法:

@Override
    public void run() {
        while (tick>0){
            System.out.println(Thread.currentThread().getName()+"完成售票,余票为"+--tick);
        }

    }

创建一个Test类,并且创建三个子线程,为了更便于观察,我们将线程名分别命名为窗口一、二、三,使用setName方法即可:

Window window1=new Window();
        Window window2=new Window();
        Window window3=new Window();

        window1.setName("1号窗口");
        window2.setName("2号窗口");
        window3.setName("3号窗口");

        window1.start();
        window2.start();;
        window3.start();

运行结果:
在这里插入图片描述
三个线程在交替进行,但从头看起,我们会发现一个问题:
在这里插入图片描述
三个窗口都出现了剩余99张票,这说明三个窗口各有100张票在卖,也就是300张,而我们的需求是3个窗口共享100张票,那么我们就要将全局变量用static修饰,这样才能对所有成员进行共享:

static int tick=100;//使用static修饰成员共享该属性

再次运行:
在这里插入图片描述
结果正常。

那么我们再使用实现Runable接口的方式完成该功能
同样我们写下一个实现类,实现run方法以及编写卖票功能代码:

public class Ticket implements Runnable{
    int tick=100;
    @Override
    public void run() {

        while (tick>0){
            System.out.println(Thread.currentThread().getName()+"完成售票,余票为:"+--tick);
        }
    }
}

注意我们这里没有对票数进行static修饰

同样使用Test类,创建并且启动线程,我们可以直接在新建Thread实例时,通过构造器重写线程名:

	Ticket ticket = new Ticket();
        Thread t1=new Thread(ticket,"窗口1");
        t1.start();
        Thread t2=new Thread(ticket,"窗口2");
        t2.start();
        Thread t3=new Thread(ticket,"窗口3");
        t3.start();

运行结果:
在这里插入图片描述发现运行正常,没有出现窗口卖票重复的情况,仔细分析一下,我们就可以得出两种方式的区别:
第一种方式是通过新建多个实现类来达到多线程的功能,所以每个实现类的属性都是不同的,如果我们不加上static修饰,那么票数就是3个100。而第二种方法,我们只新建了一个实现类的实例,最终通过创建不同的Thread实例,但传入的是同一个实现类完成多线程,也就是我们共用了一个实现类完成多线程,所以不必使用static修饰总票数。那么从代码来看,很明显第二种方式较为简单,如果我们是5个窗口进行售票,使用第一种方式就要新建5个Window类,以及其他相关的方法也要进行5次,而使用第二种方式,不管窗口数有多少,我们都只用创建一个实现类的实例,再新建不同的Thread实例即可。也就是说,第二种实现Runnable接口的方式,更适合完成这种多个线程同时处理同一份资源的需求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值