Java中的多线程-总结

线程的创建

概念:

        多线程是java中的一种功能,允许并发执行程序中的两个或者多个部分,以最大限度的利用CPU。此类程序的每个部分称为线程,因此线程是进程中的轻量级进程。

        可以使用两种机制创建线程。

  • 继承Thread类
  • 实现Runnable接口

通过继承Thread类创建线程

        我们创建一个继承java.lang.thread类的类。该类重写Thread中的run()方法。

        Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行 run方法。

        线程在run()方法中开始其生命周期。我们创建新类的对象并调用start()方法来启动线程的执行,Start()调用Thread对象的run()方法。

package test_001;

public class Test_001 {
    public static void main(String[] args) {
        //创建8个线程
        for (int i = 0; i < 8; i++) {
            Thread_test_001 thread_test_001 = new Thread_test_001();
            thread_test_001.start();
        }
    }
}
package test_001;

public class Thread_test_001 extends Thread {
    //通过extend Thread类创建线程
        public void run()
        {
            try {
                System.out.println(
                        //Thread.currentThread()该类的方法表示正在运行的线程
                        "Thread " + Thread.currentThread().getId()
                                + " 正在运行");
            }
            catch (Exception e) {
                //抛出异常
                System.out.println("Exception is caught");
            }
        }
    }

 输出

Thread 21 正在运行
Thread 25 正在运行
Thread 24 正在运行
Thread 23 正在运行
Thread 22 正在运行
Thread 20 正在运行
Thread 27 正在运行
Thread 26 正在运行

通过实现Runnable接口创建线程

        我们创建一个自定义的新类,它实现java.lang.Runnable接口并重写run()方法。然后我们实例化一个Thread对象并调用对象的start方法。

package test_001;

public class Runnable_test {
    public static void main(String[] args) {
        for (int i = 0; i <8 ; i++) {
            Runnable_001 runnable_001 = new Runnable_001();
            Thread thread = new Thread(runnable_001);
            thread.start();
        }
    }
}
package test_001;

public class Runnable_001 implements Runnable{
    @Override
    public void run() {
        try {
            System.out.println(
                    "Thread " + Thread.currentThread().getId()
                            + " is running");
        }
        catch (Exception e) {
            System.out.println("Exception is caught");
        }
    }
}

 输出

Thread 21 is running
Thread 24 is running
Thread 22 is running
Thread 23 is running
Thread 20 is running
Thread 25 is running
Thread 27 is running
Thread 26 is running

 Thread类与Runnable接口 区别与联系

  1.  如果我们扩展Thread类,我们的类就不能扩展任何其他的类,因为Java不支持多重继承,但是如果我们实现Runnable接口,我们的类任然可以扩展其他的基类。
  2. 我们可以扩展Thread类来实现线程的基本功能,因为它提供了一些Runnable接口中没有的内置方法,如yield(),interrupt()等。
  3. 当多个线程共享一个Runnable实现类的对象,意味着这些线程都将执行相同的run()方法,这对于多个线程处理相同的资源的情况非常使用,例如可能有一个任务需要在多个线程中并发执行,这时你就可以创建多个线程并让它们共享一个Runable实现类的对象来执行该任务。

java中线程的声明周期和状态

        Java中的线程在任意时刻都存在以下任意一种状态,线程在任何时刻仅处于所示状态之一

  1. 就绪状态
  2. 运行状态
  3. 阻塞状态
  4. 等待状态
  5. 定时等待状态
  6. 终止状态

解释如下

  1. 就绪状态:当新线程创建时,处于new状态。当线程处于这种状态时,线程还没有运行,当线程处于new状态时其代码未运行,且尚未开始执行
  2. 运行状态:准备运行的线程进入可运行状态,在这种状态下线程可能实际上正在运行,也可能随时准备好运行,线程调度的责任是为线程提供好运行时间。多线程程序会为每一个单独的线程分配好固定的时间量,每个线程都会运行一小段时间,然后暂停并将CPU交给另一个线程,以便其他的线程右机会运行,发生这种情况时,所有的准备运行,等待cpu的线程和当前正在运行的线程都处于可运行的状态。
  3. 阻塞:当线程试图获取锁但当前锁已被其他线程获取时,该线程进入阻塞状态,当线程获得锁时,就会冲阻塞状态进入运行状态。
  4. 等待状态:当线程调用wait()方法或者join()方法时,线程将处于等待状态,当其他线程通知该线程将终止时,它将进入可运行状态。
  5. 定时状态:当线程调用带有超时参数时,线程将处于定时等待状态,线程处于此状态,直到超时完成或者受到通知。例如,当线程调用sleep或者条件等待时,它会转入定时等待状态。
  6. 终止状态:
  • 因为它正常退出。当线程的代码已被程序完全执行时,就会发生这种情况,
  • 因为发生了一些不寻常的错误事件,例如发生了分段错误或者未处理的异常。

 在Java中实现线程的状态

在java中,要获取线程的当前状态可以使用Thread.getState()方法获取在当前状态。java中提供了java.long.Thread.State类,它定义了线程状态的ENUM常量,其摘要如下:

解释

当一个新的线程被创建时,该线程就会处于new状态,当线程调用start(0方法时,线程调度程序就会将其移到Runnable状态。每当线程实例上调用join()方法时,执行该语句的当前线程将等待该线程转移至终止状态。


Java线程中的优先级

        我们知道,Java是面向对象的,在多线程环境中工作,其中线程调度程序根据线程的优先级将处理器分配给线程。当我们在Java中创建一个线程时,它总会分配一些优先级,优先级可以右JVM在创建时指定,也可以右程序员明确指定。

        线程中的优先级是一个概念,其中每一个线程都有一个优先级每一个对象都具有优先级,用1-10范围的数字表示。

  • 默认优先级设置为5
  • 最低优先级为1
  • 最大优先级为10

java中定义了三个常量,分别如下

  1. public static int NORM_PRIORITY
  2. public static int MIN_PRIORITY
  3. public static int MAX_PRIORITY

        我们使用currentThread()方法来获取当前的线程的名称。如果用户想选择命名线程,也可以选择setName()方法

getName()方法用于获取线程的名称。

1   public final int getPriority();此方法返回给定线程的优先级 

2  public finalvoid setPriority(int newPriority);此方法将线程的优先级更改为值newPriority。如果参数newPriority的值超出最小值1和最大值10的限制,则此方法会抛出illegalArgumentException.

package test_001;

public class Priority_001  extends Thread{
    @Override
    public void run() {
        System.out.println("hhh");
    }

    public static void main(String[] args) {
        //创建5个线程对象,新建状态
        Thread t1 = new Thread();
        Thread t2 = new Thread();
        Thread t3 = new Thread();
        Thread t4 = new Thread();
        Thread t5 = new Thread();
        //获取该线程的优先级
        System.out.println("t1的线程优先级为"+t1.getPriority());
        System.out.println("t2的线程优先级为"+t2.getPriority());
        System.out.println("t3的线程优先级为"+t3.getPriority());
        //设置该线程的优先级
        t1.setPriority(2);
        t2.setPriority(4);
        t3.setPriority(8);
        System.out.println("t1的线程优先级为"+t1.getPriority());
        System.out.println("t2的线程优先级为"+t2.getPriority());
        System.out.println("t3的线程优先级为"+t3.getPriority());
        //获取主线程的相关信息
        System.out.println(Thread.currentThread().getName());
        System.out.println(Thread.currentThread().getPriority());
    }
}

输出

t1的线程优先级为5
t2的线程优先级为5
t3的线程优先级为5
t1的线程优先级为2
t2的线程优先级为4
t3的线程优先级为8
main
5

优先级说明:

        1具有最高优先级的线程将优于其他线程获得执行机,主线程的优先级默认设置为5,以后可以修改。

        2其他线程的优先级取决于父线程的优先级。

          为了照顾线程而进行的所有的处理都是在线程调度的帮助下进行的,

package test_001;

public class Thread_002 extends Thread{
    @Override
    public void run() {
        System.out.println("0000");
    }
}
package test_001;

public class Thread_002_Test {
    public static void main(String[] args) {
        //设置此主线程的优先级为6
        Thread.currentThread().setPriority(6);
        Thread_002 t1 = new Thread_002();
        System.out.println("主线程的优先级为" + Thread.currentThread().getPriority());
        System.out.println("t1的优先级为"+t1.getPriority());
    }
}

输出

主线程的优先级为6
t1的优先级为6

解释:

        默认情况下,线程的优先级为5,但是在上述代码中,我们设置了主线程的优先级是6,但是并没有设置t1的优先级,因此t1线程的优先级继承了主线程的优先级,因此它的优先级也是6. 

 输出说明

  1. 如果两个线程具有相同的优先级,那么我们无法期望哪个线程首先执行,这取决于线程的调度程序算法(循环,先来先服务等)
  2. 如果我们使用线程游戏哪集进行线程调度,那我们应该始终记住底层平台提供基于线程优先级的调度支持。

    Java中的主线程

        主线程是指程序启动时执行的线程,他是Java虚拟机JVM启动时的第一个线程,主线程执行程序main()方法,并且在main()方法执行完毕后,当所有非守护线程都结束时,JVM会正常退出。

主线程的执行流程如下

  • 在JVM启动时,会创建一个主线程,并调用main()方法,
  • main()方法中的代码会按照顺序执行,这些代码可以包括对其他线程的创建,启动和控制,等操作。
  • 当main()方法执行完毕时,主线程也会结束,但是如果程序中还有其他非守护线程在执行,主线程会等待这些线程程序执行完毕后再结束,从而保证程序的正常运行。

 如何控制主线程

        当我们程序启动时,主线程会自动创建,为了控制它,我们必须获得对他的引用——通过调用Thread类中的currentThread()方法来完成,此方法返回对其调用的线程的引用,主线程的默认优先级是5,对于所有剩余的用户线程,优先级将从父线程继承到子线程。

package test_001;

public class Main_Thread {

    public static void main(String[] args) {
        //获得主线程的引用
        Thread t1 = Thread.currentThread();
        //获取主线程的名字
        System.out.println(t1.getName());
        //获取主线程的优先级
        System.out.println(t1.getPriority());
        //更改主线程名字,优先级
        t1.setName("主线程");
        System.out.println(t1.getName());
        t1.setPriority(7);
        System.out.println(t1.getPriority());

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程");
        }
            test_thread t2 = new test_thread();
            System.out.println(t2.getName());
            System.out.println(t2.getPriority());
        t2.start();
        }
    }
package test_001;

public class test_thread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程");
        }
    }
}

输出

main
5
主线程
7
主线程
主线程
主线程
主线程
主线程
Thread-0
7
子线程
子线程
子线程
子线程
子线程

        对于每一个程序,JVM都会创建一个主线程。Main线程首先验证main方法是否存在,然后初始化该类,在JDK6开始,main方法在独立的java程序中是必需的。

使用主线程发生死锁(仅限单线程)

我们可以使用主线程(即仅使用单个线程)来创建死锁 

package test_001;

public class SiSuo {
    public static void main(String[] args) {
        try {
            System.out.println("程序进入死锁");
            Thread.currentThread().join();
            System.out.println("死锁这句不会执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出

程序进入死锁

         Thread.currentThread().join()告诉主线程等待该线程(即自身线程)死亡,因此主线程等待自己死亡,但是自身并没有死亡,所以进入了死锁。举个例子想象你是一个人类资源部门的经理,你需要等待自己的下属来汇报工作。但是,你的每个下属都认为他们需要等待你的指示才能开始工作。所以,你们陷入了一个死锁状态。这种相互等待的情况就是死锁。


Java中的Java.lang,Thread类

        线程是程序内的一行执行,每个程序可以有多个关联的线程,每个线程都有一个优先级,线程调度程序使用该优先级来确定哪个线程必须运行,Java提供了一个线程类,该类具有各种方法调用,以便通过提供构造方法来对线程执行来管理线程行为。

Java中的线程类

        线程是一个以method()启动的程序,该方法在此类中经常使用 ,称为start()方法。该方法查找run()方法(它也是此类的一个方法),并开始执行run()方法的主体。

注意,每个用做线程的类都必须实现Runnable接口并覆盖它的run方法。

           Thread的构造方法如下:

    • Thread()

      分配一个新的 Thread对象。

      Thread(Runnable target)

      分配一个新的 Thread对象。

      Thread(Runnable target, String name)

      分配一个新的 Thread对象。

      Thread(String name)

      分配一个新的 Thread对象。

      Thread(ThreadGroup group, Runnable target)

      分配一个新的 Thread对象。

      Thread(ThreadGroup group, Runnable target, String name)

      分配一个新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,属于 group引用的线程组。

      Thread(ThreadGroup group, Runnable target, String name, long stackSize)

      分配一个新的 Thread对象,以便它具有 target作为其运行对象,将指定的 name正如其名,以及属于该线程组由称作 group ,并具有指定的 堆栈大小

      Thread(ThreadGroup group, String name)

      分配一个新的 Thread对象。


Java多线程中的start()函数的作用

        创建线程时,我们重写或者实现run()函数,但是我们通过调用start()函数来启动一个线程,那我们为什么不直接调用重写的run()函数呢?为什么总是调用start函数来执行线程呢?

调用函数会发生一下操作

  1. 方法调用,当一个方法调用时,程序会调用堆栈中创建一个新的栈帧来存储方法的局部变量,参数以执行过程中的临时变量。
  2. 入栈,被调用的方法的栈帧被压入堆栈的顶部。
  3. 执行 :执行被调用的方法,其中包括执行方法的代码和操作。
  4. 返回:方法执行完成后,返回该方法的调用点,这时方法的栈帧会从调用栈中弹出,控制权回到调用该方法的地方。

        总的来说调用栈是一个后进先出的数据结构,因此最后调用的方法会先返回。

        start()方法的目的是为线程创建一个单独的调用堆栈。它会创建一个单独的调用堆栈,然后由JVM调用run()

package test_001;

public class run_test {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            run_Thread r1 = new run_Thread();
            //用run代替start
            r1.run();
        }
    }
}
package test_001;

public class run_Thread extends Thread{
    @Override
    public void run() {
        System.out.println("线程"+Thread.currentThread().getId()+"正在执行");
    }

}

输出

线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行 

        从上面的输出可以看到,我们为所有的线程获得了相同的id,因为我们直接调用了run()。调用start的程序打印不同的id如下

当调用start方法时

线程20正在执行
线程23正在执行
线程24正在执行
线程21正在执行
线程22正在执行


Java中Thread类中的start方法和run方法的区别

        1新线程的创建:当程序调用start方法时,会创建一个新的线程,然后执行run方法。但是,如果我们直接调用run方法,则不会创建新线程,并且run方法将会当做当前调用线程的普通方法来执行。

调用start方法

package test_001;

public class run_test {
    public static void main(String[] args) {
        run_Thread r1 = new run_Thread();
        r1.start();
    }
}
package test_001;

public class run_Thread extends Thread{
    @Override
    public void run() {
        System.out.println("正在运行的线程"+Thread.currentThread().getName());
    }

}

输出:

正在运行的线程Thread-0

        解释:如上述示例,我们调用线程的start方法时,会创建一个默认名称为Thread的新线程,然后调用run方法,并在该线程上执行其中的所有的内容,新创建的线程,现在我们尝试调用run方法而不是start方法

调用run方法

package test_001;

public class run_test {
    public static void main(String[] args) {
        run_Thread r1 = new run_Thread();
        r1.run();
    }
}
package test_001;

public class run_Thread extends Thread{
    @Override
    public void run() {
        System.out.println("正在运行的线程"+Thread.currentThread().getName());
    }

}

 输出

正在运行的线程main

        1.当我们调用run_test类中的run方法时,不会创建新的线程,而是在当前线程(即主线程)上执行run方法。因此没有发生多线程,run方法作为普通的方法进行调用。

        2.多次调用:在java的多线程概念中,start方法和run方法之间的另一个最重要的区别就是我们不能调用start方法两次,否则会抛出IllegalStateException,而run方法可以被调用多次,因为这只是正常的方法调用,具体解释如下 

两次调用run方法

package test_001;

public class run_test {
    public static void main(String[] args) {
        run_Thread r1 = new run_Thread();
        r1.run();
        r1.run();
    }
}
package test_001;

public class run_Thread extends Thread{
    @Override
    public void run() {
        System.out.println("正在运行的线程"+Thread.currentThread().getName());
    }

}

输出:

正在运行的线程main
正在运行的线程main 

 两次调用start方法

package test_001;

public class run_test {
    public static void main(String[] args) {
        run_Thread r1 = new run_Thread();
        r1.start();
        r1.start();
    }
}
package test_001;

public class run_Thread extends Thread{
    @Override
    public void run() {
        System.out.println("正在运行的线程"+Thread.currentThread().getName());
    }

}

输出

Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:708)
    at test_001.run_test.main(run_test.java:7)
正在运行的线程Thread-0

         两次调用start方法会发生 java.lang.IllegalThreadStateException异常。

概括对比

                start()run()
创建一个新线程,并在新线程的线程上运行run()方法。不会创建新线程,并且run()方法在调用线程本身上运行。
不能多次调用,否则会跑出java.lang.IllegalThreadStateException可以多次调用
定义在java.lang.Thread类中在java.long.Runnable接口定义,必须在实现类中重写

Java中的Thread.sleep()方法及其示例

        Thread类是一个基本上程序执行线程的类,它存在于java.long包中,Thread类包含Slee()方法,Thread类中存在两种Sleep方法,一种是带一个参数的方法,一种是带两个参数的构造方法。

    • static voidsleep(long millis)

      使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),

      具体取决于系统定时器和调度程序的精度和准确性。

      static voidsleep(long millis, int nanos)

      导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),

      这取决于系统定时器和调度器的精度和准确性。

         sleep方法是用于在特定的时间内停止当前线程,(无论系统中可能正在执行的线程),并且在改时间段结束后,较早执行的线程开始执行。

sleep方法要点:

  • 每当sleep函数执行时,它总是暂停当前线程的执行。
  • 如果有其他线程调用了该线程的interrupt()方法,那么休眠的线程会被中断,并抛出InterruptedException异常。这是一种可中断的休眠,意味着线程在休眠期间可以被其他线程唤醒或中断
  • 系统负载对休眠的影响:sleep方法传入的参数代表该休眠的时间长度(毫秒数)然而,实际上线程的休眠时间可能会受到系统负载的影响,当系统繁忙时,线程可能会休眠更长时间,而当系统负载较小时,线程实际休眠会接近传入sleep方法的参数指定时间,这是因为在系统繁忙时,调度器可能会推迟线程的唤醒时间,以便为更多的线程分配cpu时间片。

        总之,sleep()方法允许线程在指定的时间段内休眠,但是它并不保证线程将在指定的时间后立即唤醒。实际的休眠时间可能会受到系统负载的影响,并且线程在休眠期间可能会被其他线程中断。 调用了sleep()方法会导致线程进入阻塞状态,在阻塞状态下,不会占用cpu资源,直到指定的休眠时间到达或者线程被中断才会进入就绪状态,等待cpu的分配。

再次说明线程的状态

  1. 就绪状态(Ready):线程已经被创建,但是还没有开始执行。它等待系统分配CPU时间片以便执行。
  2. 运行状态(Running):线程正在执行其任务,占用CPU资源。
  3. 阻塞状态(Blocked):线程暂时停止执行,不会占用CPU资源。线程可能会处于阻塞状态等待获取锁、等待输入/输出、等待系统资源等。
  4. 等待状态(Waiting):线程等待其他线程的通知或者特定条件的满足,以便继续执行。
  5. 超时等待状态(Timed Waiting):类似于等待状态,但是在等待一定时间后会自动返回。
  6. 终止状态(Terminated):线程执行完毕或者由于异常而提前终止。

——完毕

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱喝醋的雷达

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值