JAVA多线程(一)

JAVA多线程(一),第1张

目录

程序、进程与线程

继承Thread类

实现Runnable接口

Thread类常用的构造方法

 继承Thread类与实现Runnable接口的区别

编写线程类继承Thread类

 编写线程类实现Runnable接口

实现Callable接口

线程的生命周期

 线程的常用方法

 例一:线程的常用方法一

例二:自定义线程的默认名称

 例三:线程是否处于活跃状态

 例四:线程的强制执行

例五:线程的休眠

例六:线程的礼让


程序、进程与线程

程序是一系列有序的指令集合,由程序员编写、由代码组成,用于让计算机执行一定
的 *** 作或用于实现一定的功能。
启动运行的程序被称为进程,系统会为进程分配一定的内存空间,进程由三部分组
成,分别为cpu、data和code.
线程是进程的组成部分,一个进程中有N多个线程,这些线程共享进程的内存资源,
线程负责具体的执行。

进程与进程之间是相互独立的,各个线程之间互相“抢占”CPU资源,哪个线程“抢占”到了CPU资源,CPU就会执行哪个进程中的线程,CPU执行线程的过程被称为CPU的调度。在一个时间片内CPU只能执行一个线程,当时间片结束后再调用执行其他的线程。所以当一个进程中的线程多了,那么被CPU调用执行的概率就大,所以多线程 *** 作可以提高程序的运行效率

在Java中实现多线程的方式有三种,一种是继承Thread类,一种是实现runnable接口,第三种是从JDK1.5开始的通过callable接口实现多线程

继承Thread类

一个普通类继承 Thread类,这个类就被称为具备多线程 *** 作能力的类,具备多线程
能力的类,要求重写父类Thread类中的run()方法,在run()方法中所编写的代码被称为
线程体
 

//继承Thread类,让自定义的类MyThread具备多线程 *** 作的能力
public class MyThread extends Thread{
    //重写run方法
    @Override
    public void run(){
    //编写线程体(线程所执行的代码)
      System. out. println("MyThread中的run方法中的代码”);
    }
}



public class TestMyThread {
   //主方法负责java程序的运行,被称为主线程
   public static void main(String[] args) {
   //创建线程类的对象
   MyThread my=new MyThread();
   //启动线程
   my.start();

System, out. println("-----------main方法中的代码");
/**在以上代码中,至少有两个线程,一个是主线程,一个是my线程*/
   }
}


运行Java程序时启动了Java虚拟机,负责执行主线程(主方法)中的代码,在执行
MyThread my=new MyThread()之前有一个线程,线程的名称为main,是主线程,使用
my.start()启动my线程,这个时候有两个线程,一个是主线程,一个是my线程,由于哪个线程先“抢占”到CPU资源不确定,所以两句代码哪句先执行也不确定,执行结果如下图。
 
在线程体中使用循环,程序的运行效果会更加明显
【示例16-3】线程体中使用循环。

package cn. sxt.thread;
//继承Thread类,让自定义的类MyThread2具备多线程 *** 作的能力

public class MyThread2 extends Thread{
//重写run方法
@Override
    public void.run(){
        for(int i=0;i<10;i++){
         System.out.println("MyThread2 中的 i="+i+"--------------"
        }
    }
}


public class TestMyThread2 {
  public static void main(String[] args) {
//创建MyThread2线程类的对象
MyThread2 my2=new MyThread2();
//启动线程
my2.start();
//主线程中的循环
    for(int i=0;i<10;i++){
    System.out.println("============主方法中的for循环i="+i);
    }
  }
}

运行效果如下图所示

 
在启动线程时使用的是Thread类中start()方法,而非调用run()方法,如果使用my2.run(),那么线程my2将不会被启动,那么只有一个线程,那就是主线程,程序无论运行多少次,结果都相同。

实现Runnable接口

Runnable接口中没start方法,要想启动线程,必须借助Thread类

Thread类常用的构造方法
Thread()创建Thread对象
Thread(String name)创建Thread对象,并给线程起个名字
Thread(Runnable target)创建Runnable接口的实现类创建Thread对象
Thread(Runnable target,String name)根据Runable接口的实现类创建Thread对象,并给线程起个名字
//MyRunnable实现Runnable接口,MyRunnable就具备了多线程 *** 作的能力
public class MyRunnable implements Runnable {
    //必须实现接口中的run()方法
    @Override
    public void run() {
        //编写线程体,线程所要执行的代码
        for (int i=0;i<5;i++){
            System.out.println("Myrunnable类中run方法中的i="+i);
        }
    }
}

class testMyRunnable{
    public static void main(String[] args) {
        MyRunnable myRun=new MyRunnable();
        //myRun.start();是不可以启动线程的,因为Runnable接口中没start方法,要想启动线程,必须借助Thread类
        Thread t=new Thread(myRun);//根据Runnable接口的实现类创建Thread对象
        t.start();
        //主线程中的代码
        for (int i=0;i<5;i++){
            System.out.println("============main中的i="+i);
        }
    }
}

 代码运行效果

 继承Thread类与实现Runnable接口的区别

Java中的类具有单继承的特点,如果一个类继承了Thread类,那么就不能再继承他的类了,所以继承Thread类实现多线程有一定的局限性。
使用Runnable接口实现多线程的优点如下:
(1)避免单继承。
(2)方便共享资源,同一份资源可以有多个代理访问。

例如:有三个窗口共卖5张票,每个窗口有1100个人排队,分别使用继承Thread类和Runnable接口实现。

编写线程类继承Thread类
public class TicketThread extends Thread {
    public TicketThread(String name){
        super(name);//调用父类Thread类中带参的构造方法
    }

    private int ticket=5;//共享资源5张票
    @Override
    public void run() {
        //编写变成体,100个人
        for (int i=0;i<100;i++){
            //判断是否有票
            if (ticket>0){
                System.out.println(super.getName()+"卖第"+(ticket--)+"张票");
            }
        }
    }
}
class TestTicketThread{
    public static void main(String[] args) {
        //创建三个线程类的对象
        TicketThread t1=new TicketThread("A窗口");
        TicketThread t2=new TicketThread("B窗口");
        TicketThread t3=new TicketThread("C窗口");
        t1.start();//启动线程,开始售票
        t2.start();
        t3.start();
    }
}

测试结果

 由测试结果可以看到每个窗口各卖了5张票,3个窗口一共卖了15张,产生的原因是三个线程类的对象,每个对象都有一个独立的属性ticket。其内存分析图如图

 编写线程类实现Runnable接口
public class TicketRunnable implements Runnable {
    //共享资源5张票
    private int ticket=5;
    @Override
    public void run() {
        //编写线程体
        for (int i=0;i<100;i++){
            //判断是否有票
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+"卖第"+(ticket--)+"张票");
            }
        }
    }
}
class TestTicketRunnable{
    public static void main(String[] args) {
        //创建线程类的对象
        TicketRunnable tr=new TicketRunnable();
        //用一个Runnable的实现类创建三个代理类的对象
        Thread t1=new Thread(tr,"A窗口");
        Thread t2=new Thread(tr,"B窗口");
        Thread t3=new Thread(tr,"C窗口");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

测试结果

通过实现Runnable接口来实现多线程,达到了多个线程访问共享资源的目的。无论是继承Thread类还是实现Runnable接口,run()方法都是没有返回值的,而且如果在线程体中有异常需要抛出也是很难实现的。

在JDK1.5之后实现多线程还可以使用第三种方式,那就是实现Callable接口,重写call()方法。

实现Callable接口

Callable接口从JDK1.5开始,与Runnable实现多线程相比,Callable支持泛型,call()方法可以有返回值,而且还支持泛型的返回值,比run()方法更强大的一点是还可以抛出异常。
Callable接口中的方法call()需要借助 FutureTask类来获取结果,使用Callable接口实现多线程所需要的类与接口之间的关系如下图所示


任务管理器 FutureTask 是 RunnableFuture 接口的实现类,而 RunnableFuture 接口又继承了Future接口和Runnable接口,所以任务管理器类 FutureTask 也是Runnable接口的实现类。通过创建任务管理器类的对象将Callable接口的实现类传入,从而实现多线程,通过实现Callable接口实现多线程

//创建一个类实现Callable接口
import java.util.concurrent.Callable;
public class RandomCallable implements Callable {
    @Override
    public String call() throws Exception {
        //创建一个长度为5的String类型的数组
        String []array={"apple","banana","orange","grape","pear"};
        int Random=(int)(Math.random()*4)+1;//产生一个1~4之间的随机数
        return array[Random];//根据产生的随机数从而随机获得数组中的元素
    }
}

//测试类
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建任务
        RandomCallable rc=new RandomCallable();
        //创建任务管理器,将任务提交给任务管理器
        FutureTaskft=new FutureTask<>(rc);
        //创建Thread类
        Thread t=new Thread(ft);//FutureTask是Runnable接口的实现类,用Runnable接口的实现类创建Thread类
        System.out.println("任务是否已完成:"+ft.isDone());
        //启动线程
        t.start();
        //获取返回值结果
        System.out.println(ft.get());
        System.out.println("任务是否已完成:"+ft.isDone());
    }
}

测试结果 

 

 在start()方法之前,任务是没有完成的,因为还没有启动线程,所以isDone()的结果为false,当使用get()方法获取到结果后,说明任务已经完成了,因为只有结果出来任务才能结束,否则无论get()方法后有多少句代码都不会执行。

线程的生命周期

一个线程类使用new关键字创建完对象,在堆内存开辟内存空间,这个线程就处于新生状态,所以说处于新生状态的线程有自己的内存空间。
使用start()方法开始启动线程,那么线程处于就绪状态,处于就绪状态的线程具备了运行的所有条件,但是还没有得到CPU的调度,通常被称为“万事俱备,只欠CPU调度”。就绪状态的线程具备了运行资格,不具备运行条件。
当系统选定一个等待执行的线程后,这个线程将由就绪状态进入执行状态,被称为运行状态,运行状态的线程执行自己的run方法中的代码,直到等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统换下来回到就绪状态。
处于运行状态的线程在调用了join()、sleep()、wait()或者等待I/O等时,线程则处于阻塞状态。当引起阻塞的原因消除时,线程就会转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停下的位置开始继续执行。
一个线程的run()方法执行完毕或者抛出未捕获的异常,再或者调用stop()方法,那么线程将处于死亡状态,线程的生命周期如下图

 线程的常用方法

Thread类提供了N多 *** 作线程的方法

 例一:线程的常用方法一

例二:自定义线程的默认名称

 例三:线程是否处于活跃状态

 运行结果

 对运行结果的分析

使用new创建线程类的对象后,线程还未处于活动状态,所以isAlive()的结果为false。当调用start()方法启动线程后,线程处于就绪状态,处于就绪状态的线程处于活动状态,isAlive()的结果为true。

在运行效果图中可以看到“主线程结束,my2线程是否处于活动状态”的结果为true或者为false,也就是说my2线程是否处于活动状态,取决于主线程与my2线程谁先执行结束。

如果主线程先结束,再打印输出时my2线程还没有结束,所以依然处于活动状态,如果my2线程先结束,主线程还没有结束,当打印输出时my2线程就处于非活动状态。

 例四:线程的强制执行

 测试结果

 

 当主线程中i>2时,代理线程t调用join方法强制执行,主线程处于阻塞状态;当代理线程t执行结束时,主线程才能继续执行。

我们也可以通过调用sleep()方法让线程进入休眠状态,休眠状态的线程本身进入阻塞状态

例五:线程的休眠

执行过程

Java多线程休眠

 在线程类的run方法中使用了Thread.sleep(500)线程休眠0.5秒,所以在程序启动运行0.5秒后才开始进行输出,也就是说每次执行到Thread.sleep(500)时,线程都将进入阻塞状态,0.5秒后进入就绪状态,然后再次等待CPU调度执行。

例六:线程的礼让

yield()方法使当前线程暂停一次,允许其它线程执行,yield()的线程不会进入阻塞状态,而是直接进入就绪状态。让了其他线程后,下一个就是自己占据CPU的资源了。如果没有其它等待的线程,则yield()的线程会马上恢复执行。

 运行结果

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/798611.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存