今日只为你狂欢-----JAVA线程总结(零基础入门)
Posted 敲代码的xiaolang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了今日只为你狂欢-----JAVA线程总结(零基础入门)相关的知识,希望对你有一定的参考价值。
😊😘文章目录😘😊
😎前言
今天——1024,全国程序员的狂欢节,博主之前曾经设立了一个投票,想看看大家对博主那个专栏更感兴趣,根据结果,发现JAVA的热度是最高的,所以笔者按照约定,在此奉上此篇长文,好好看看JAVA里面的线程世界。笔者是小白一枚,文中若有不足之处,还请各位大佬不吝赐教,如果你觉得不错,那么一键三连是对笔者最大的肯定。😁😁😁
🧐一、线程是什么?
你在约会,你的女朋友问你JAVA里面的线程是什么?你可以这样回答:
我们的世界在同时进行着许多的事情,比如你的小狗狗在呼吸的时候,它也可以进行思考,比如思考今天吃啥。我们人也如此,比如你在睡觉的时候,那你当然除了呼吸以外,你的心脏也在跳动。那么这些同时完成的事情,在JAVA的世界里面,就叫做并发,那么将并发完成的每一件事情叫做线程。
在编程的世界里面,不是所有的程序语言都可以支持我们的线程,一般都是一个任务完成之后,再进行下一个任务,前提就是等我们前一个任务结束。而我们的JAVA却为我们提供了并发机制,我们可以执行多个线程,每一个线程可以搞定一个功能,这种机制也就是多线程。
百度百科上对线程有着如下定义:
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
emmm。。。可能你会问,什么是进程?
所谓进程就是操作系统中一段程序的执行过程。更全面来讲:进程是一个具有一定独立功能的程序操作系统中关于某个数据集合进行的一次运行活动。是操作系统程序动态执行的基本单元。在传统的操作系统中,进程既是一个操作系统的基本分配单元,也是操作系统的基本执行单元。
总的来说:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
🤔二、如何实现线程?
❤️❤️1.继承java.lang.Thread类❤️❤️
首先,Thread是java.lang包里的一个类,如果你在这个包里实例化一个对象,那么这个被实例化的对象就叫做线程,我们每启动一个新的线程时就要建立Thread实例,那么如何来进行构造呢?
菜鸟教程上这么写道:
创建一个线程的方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
说白了,就是下面这代码:
public class ThreadTest extends Thread{
//继承Thread类创建一个新的线程的语法
}
我们的线程的实现真正功能的代码放在类的run()方法里面,如果我们有一个类继承了Thread类之后,那么我们就可以在该类里面覆盖run()方法,将实现此线程的方法写入到run()里面,然后同时调用Thread类里面的start()方法执行线程,本质上来讲就是调用run()方法。
对于Thread对象需要一个任务来执行,任务是指线程在启动的时候执行的工作,那么该工作的功能代码被写在run()方法里面,我们的run()方法需要使用以下的格式:
public void run(){
}
当我们执行一个线程程序的时候,就自动产生了一个线程,我们的主方法也就是在这个线程上进行的,当不再启动其他线程的时候,这个线程就被叫做单线程程序。我们主方法线程启动由JAVA虚拟机负责,我们只用来负责自己的线程。
比如下面的代码:
public static void main(String[] args){
new ThreadTest().start();
}
菜鸟教程上面给了我们举了很多例子:
我们来看一个栗子:
public class ThreadTest extends Thread { // 指定类继承Thread类
private int count = 10;
public void run() { // 重写run()方法
while (true) {
System.out.print(count+" "); // 打印count变量
if (--count == 0) { // 使count变量自减,当自减为0时,退出循环
return;
}
}
}
public static void main(String[] args) {
new ThreadTest().start();
}
}
我们的ThreadTest继承了Thread类,然后在该类里面覆盖了run()方法,本实例使用count–的方法跳出了循环,从而避免了run()方法中无限循环的形式。在我们的main方法里面,使线程执行需要调用Thread类当中的start()方法,我们的start()方法调用被覆盖的run()方法,如果不调用start()方法,线程永远不会启动,在主方法没有调用start()方法之前,Thread方法对象只不过是一个实例,但是不能算作一个真正的线程。
🤞🤞2.实现java.lang.Runnable接口🤞🤞
通过上面的了解,我们知道了线程大多是Thread类来创建的,那么如果我们要继承其他的类呢?我们就饿可以使用Runnable接口来实现。
我们看一下语法:
public class Thread extends Object implements Runnable
其实我们查阅API文档,可以发现,Thread类实现了Runnable接口,其中的run()方法正是对Runnable接口里面run方法的具体实现。
菜鸟教程上写道:
在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。
Thread类中有以下两个构造方法:
public Thread(Runnable target)
public Thread(Runnable target,String name)
这里,target 是一个实现 Runnable 接口的类的实例,并且 name 指定新线程的名字。新线程创建之后,你调用它的 start() 方法它才会运行。
菜鸟教程给出下面的实例:
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
输出结果:
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
🥰三、线程的生命周期
线程共有七种生命状态:出生、就绪、运行、等待、休眠、阻塞、死亡。
在《JAVA入门到精通(第五版)》里面有写:
出生状态就是线程被创建时所处的状态,在用户使用该线程实例调用start()方法之前,线程都处于出生状态,当用户调用start()方法之后,线程就处于就绪状态,当线程得到系统的资源之后就成为了运行状态。一旦我们的线程进入可执行的状态,它就会在就绪与运行状态下转换,同时也可能进入等待、休眠、阻塞或者死亡的状态。当处于运行状态下的线程调用Thread类里的wait()方法时,该线程便进入到了等待状态,进入等待状态的线程必须调用Thread类的notify()方法才能够被唤醒,我们的notifyAll()方法是将所有处于等待状态下的线程唤醒。当线程调用Thread类中的sleep()方法时,则会进入休眠状态。如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入输出结束时,就进入到了就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程也不能够回到运行状态。当线程的run()方法执行完毕的时候,线程进入死亡状态。
🤗🤗1.线程的休眠🤗🤗
你在约会,你女朋友困了,所以需要休息,在JAVA的线程世界里面,使用sleep()方法,可以使用一个参数用于指定该线程的睡眠时间,这里的时间是以毫秒为单位,一般而言,我们的sleep()方法通常放入到run()方法里面,比如下面这个例子,此代码会将线程在2s内无法进入就绪状态:
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
//printStackTrace()方法:打印异常信息在程序中出错的位置及原因
我们再看一个栗子:
public class Main {
public static void main(String[] args) {
TestSleep();
}
public static void TestSleep(){
int num = 5;
while (num>=0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
}
}
}
你可以自己跑一下,你会发现运行框里是每一秒运行出一个结果:
🤣🤣2.线程的加入🤣🤣
你在约会,然后你外卖到了,这个时候,你需要先去拿外卖,然后再来约会。其实在线程的世界里面,比如你有一个多线程程序,假如你有一个线程A,现在你需要把线程B插入到里面,这个时候,我们就可以使用Thread里面的join()方法,当我们某个线程使用join()方法加入到另一个线程里面的时候,另一个线程会等待该线程执行完毕后,再进行执行。
我们可以看一个栗子,在没加入别的线程之前:
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(Main::print);
t1.start();
System.out.println("Done.");
}
public static void print() {
for (int i = 1; i <= 5; i++) {
try {
System.out.println("Counter: " + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
双冒号是JDK8的新特性,对类中静态方法的使用
运行结果:
加入别的线程之后:
public class Main {
public static void main(String[] args) {
Thread t1;
t1 = new Thread(Main::print);
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Done.");
}
public static void print() {
for (int i = 1; i <= 5; i++) {
try {
System.out.println("Counter: " + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//双冒号是JDK8的新特性,对类中静态方法的使用
运行出来的结果是:
当我们加入join()方法后,原来的结束done只能等待加入的线程结束后,才能够被打印出来。
🤨🤨3.线程的中断🤨🤨
你在约会,你女朋友外卖到了,她去取外卖了,你的约会被中断了,其实中断也就是停止我们的线程。JAVA里面有三种常见的中断线程的方式:
public void interrupt()
//表示中断当前线程,仅仅设置一下线程的中断标记位。
public static boolean interrupted()
//它将返回当前线程的中断位是否被标记
public boolean isInterrupted()
//无论当前线程是否被中断了,都不会清空中断标志位。
我们看一个代码,我们只需要注意标红部分即可,这个代码即将某个线程使用interrupted()方法,同时给我们抛出了InterruptedException异常,在异常处理的时候便结束了while循环,由于调用了interrupted()方法,所以抛出了对应的InterruptedException异常。这个代码其他部分是创建了InterruptedSwing类,然后将该类实现了Runnable接口,创建了一个进度条,然后在这个表示进度条的线程里面使用了interrupted()的方法:
然后我们看一下运行的结果:
🥳四、线程的优先级
你在约会,你女朋友让你去买饮料,你应当听你女朋友的,乖乖去买并且回来的时候最好带点别的小礼物回来。在JAVA的世界里面,当我们的很多线程位于就绪状态的时候,系统会根据优先级来决定让哪一个线程进入到运行状态,比如我们的java里面的垃圾回收机制,一般而言垃圾回收线程的优先级就比较低。每一个新产生的线程都继承了父线程的优先级。
在我们的多任务的操作系统里面,每一个线程都会得到一个小的CPU来运行,当这一段属于它们的小段CPU时间段运行结束时,就会换到另一个线程进入运行的状态。
我们的线程优先级可以使用setPriority()方法来进行调整,如果使用该方法的优先级不在1~10里面,将会产生IllegalArgumentException的异常,这里需要提一下Thread类中包括的成员变量代表了线程的某些优先级,如Thread.MIN_PRIORITY(常数 1)、Thread.MAX_PRIORITY(常数 10)、Thread.NORM_PRIORITY(常数 5)、每一个线程的优先级都在Thread.MIN_PRIORITY(常数 1)与Thread.MAX_PRIORITY(常数 10)之间。
我们看一个代码:
import java.awt.*;
import javax.swing.*;
public class PriorityTest extends JFrame {
private static final long serialVersionUID = 1L;
private Thread threadA;
private Thread threadB;
private Thread threadC;
private Thread threadD;
public PriorityTest() {
getContentPane().setLayout(new GridLayout(4, 1));
// 分别实例化4个线程
final JProgressBar progressBar = new JProgressBar();
final JProgressBar progressBar2 = new JProgressBar();
final JProgressBar progressBar3 = new JProgressBar();
final JProgressBar progressBar4 = new JProgressBar();
getContentPane().add(progressBar);
getContentPane().add(progressBar2);
getContentPane().add(progressBar3);
getContentPane().add(progressBar4);
progressBar.setStringPainted(true);
progressBar2.setStringPainted(true);
progressBar3.setStringPainted(true);
progressBar4.setStringPainted(true);
threadA = new Thread(new MyThread(progressBar));
threadB = new Thread(new MyThread(progressBar2));
threadC = new Thread(new MyThread(progressBar3));
threadD = new Thread(new MyThread(progressBar4));
setPriority("threadA", 5, threadA);
setPriority("threadB", 5, threadB);
setPriority("threadC", 4, threadC);
setPriority("threadD", 3, threadD);
}
// 定义设置线程的名称、优先级的方法
public static void setPriority(String threadName, int priority,
Thread t) {
t.setPriority(priority); // 设置线程的优先级
t.setName(threadName); // 设置线程的名称
t.start(); // 启动线程
}
public static void main(String[] args) {
init(new PriorityTest(), 100, 100);
}
public static void init(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
private final class MyThread implements Runnable { // 定义一个实现Runnable接口的类
private final JProgressBar bar;
int count = 0;
private MyThread(JProgressBar bar) {
this.bar = bar;
}
public void run() { // 重写run()方法
while (true) {
bar.setValue(count += 10); // 设置滚动条的值每次自增10
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("当前线程序被中断");
}
}
}
}
}
运行结果(其实是第一个进度条先变换,C和D的优先级在后面,我们用肉眼可能比较难识别):
我们通过一张图更好的理解:
我们优先级为5的线程A首先得到CPU,结束后,B才会得到机会执行,当B执行完后继续执行A,直到A和B都执行完毕才会轮换到C执行,当C执行完成后才会轮换到D继续执行。
🥴五、线程的同步
你在约会,你陪你对象玩,你和你对象应该是同时进行玩或者说话,不可能把你变成个机器人,接收到指令才会行动。在JAVA的世界里,单线程的程序只能每一次做一件事,但是后面的事只能等待前面的事完成后才能继续执行,如果我们使用多线程,就有可能发生两个线程同时抢占同一个资源,我们的JAVA提供了线程同步机制来防止资源之间的访问冲突。
👾👾1.线程安全👾👾
你在约会,你的女朋友想去看电影,但是那个场次只剩下一张票了,如果我们的电影院在设计售票系统的时候没有考虑到票如果售空了该怎么办,那么可能会出现下面的这种情况:
public class Main implements Runnable {
int num = 10; // 设置当前总票数
public void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("tickets" + num--);
}
以上是关于今日只为你狂欢-----JAVA线程总结(零基础入门)的主要内容,如果未能解决你的问题,请参考以下文章
Android零基础入门第70节:ViewPager轻松完成TabHost效果