有没有一种可能,我可以学好Java的多线程——知识点汇总(一)

简介: 有没有一种可能,我可以学好Java的多线程——知识点汇总(一)

一、前言

大家好,我是卷心菜,大二学生一枚。这篇文章是介绍Java基础知识——多线程的,读完这篇文章,各位小伙伴们可以收获哪些呢?


程序、进程、线程三者的区别

创建线程的四种方式

线程的常用方法

线程的死锁、释放锁

线程的同步原理、生命周期等等

废话不多说,干货满满,赶快来看看吧~


二、基本概念

什么是程序?


程序是为完成特定任务、用某种语言编写的一组指令的集合。大白话来讲,就是我们写的代码

什么是进程?


进程是程序的一次执行过程,或是正在运行的一个程序。例如运行中的QQ、微信,运行中的MP3播放器等等,有它自身的产生、存在和消亡的过程

什么是线程?


线程是由进程创建的,是进程的一个实体,一个进程可以拥有多个线程,也可以有单个线程

image.png


三、创建线程

1、方式一

继承Thread类,看代码:


public class Thread_one {
    public static void main(String[] args) throws InterruptedException {
        Person person = new Person();
        person.start();
        //当启动线程时,子线程和主线程会交替执行
        //主线程结束,子线程不一定结束
        //person.run()就是一个普通的方法,没有真正的启动一个线程
        for (int i = 0; i < 20; i++) {
            System.out.println("main线程执行");
            Thread.sleep(1000);
        }
    }
}
class Person extends Thread {
    @Override
    public void run() {
        int times = 0;
        while (true) {
            try {
                System.out.println("大家好,我是卷心菜~"
                        + (++times)
                        + "号" + "线程名是——"
                        + Thread.currentThread().getName());
                Thread.sleep(2000);
                if (times == 50) break;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



该代码的功能是:启动一个子线程,在控制台打印50次“大家好,我是卷心菜”,主线程在控制台打印“main线程执行”,运行的部分结果如下:

image.png

我们来重点剖析一下这种创建方式,因为后面三种方式本质上区别不大。


当我们Person类继承Thread类以后,就要重写run(),方法体内写上自己需要的代码逻辑。语句Thread.sleep(2000);意思是,每打印一句话在控制台后,子线程就要“休息”2秒,在这“休息”的时间,主线程就开始执行自己的语句;同样,在主线程“休息”的时间,子线程也开始执行自己的语句。


总之,子线程和主线程会交替执行,多次运行可以发现,主线程结束,子线程不一定结束。这里需要特别注意。


爱思考的小伙伴们可能就要问了:我们在Person类中重写了run(),但是怎么使用线程的时候,调用的是start(),那run()谁来执行的呢?别着急,让我们进去源码看一看。

image.png

我们从源码中可以发现,start()调用了start0(),而start0()是一个本地方法,由JVM调用,只是我们看不到而已。


image.png

2、方式二

方式一明白了,再看看第二种方式,代码如下:


public class Thread_two {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Thread thread = new Thread(animal);
        thread.start();
    }
}
class Animal implements Runnable {
    int times = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("大家好,我是Tomcat~");
            try {
                Thread.sleep(2000);
                ++times;
                if (times == 10) break;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



此代码逻辑也很好理解,这里就不过多讲述了。我们来分析一下:为什么要使用类实现接口的方式来创建线程?


学过基础的小伙伴们都知道,我们Java是单继承的。因此,在某些情况下一个类可能已经继承了某个类,这时再用继承Thread类方法来创建线程就不可以了,所以我们有了Runnable接口。


3、方式三

再来看看方式三,代码如下:


public class Thread_six {
    public static void main(String[] args) throws Exception {
        D d = new D();
        FutureTask futureTask = new FutureTask(d);
        Thread thread = new Thread(futureTask);
        thread.start();
        Object o = futureTask.get();
        System.out.println(o);
    }
}
class D implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            System.out.println(i + 1);
            sum += i;
        }
        return sum;
    }
}


来看看运行结果:

image.png

与Runnable类相比,类Callable功能更强大些,具体有如下优点:


相比run()方法,可以有返回值

方法可以抛出异常

支持泛型的返回值

需要借助FutureTask类,比如获取返回结果

4、方式四

最后来看看第四种方式,使用线程池,也是最推荐使用的方式,代码如下:


public class Thread_seven {
    public static void main(String[] args) {
        ExecutorService service =
                Executors.newFixedThreadPool(10);
//        service 可以理解为池子
        service.execute(new F()); //适用于Runnable
//        service.submit();  //适用于Callable
        service.shutdown();//关闭线程池
    }
}
class F implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(i + 1);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



运行结果没有任何问题:

image.png


四、常用方法

1、第一组

public Thread(Runnable target):返回对当前正在执行的线程对象的引用

public synchronized void start():开启线程

public final synchronized void setName(String name):设置线程名

public final void setPriority(int newPriority):设置线程的优先级别

public static native void sleep(long millis):设置线程的睡眠时间

public void interrupt():中断线程,但并没有结束线程,一般用于中断正在休眠的线程

这么多的方法,用代码实践一下:


public class Thread_five {
    public static void main(String[] args) {
        A a = new A();
        Thread thread = new Thread(a);
        thread.setName("棒棒糖线程");
        thread.setPriority(Thread.MIN_PRIORITY);
        thread.start();
        thread.interrupt();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程开始行动");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class A implements Runnable {
    @Override
    public void run() {
        while (true) {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("我要吃" + (i + 1) +
                            "棵棒棒糖,所处的线程是:"
                            + Thread.currentThread().getName());
                }
                System.out.println("吃多了,要消化20秒");
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}



带大家看一下线程的优先级:


MAX_PRIORITY:10

MIN _PRIORITY:1

NORM_PRIORITY:5

截取一部分运行结果:

image.png

解析一下:由于把子线程的优先级设置为最低,主线程拿到了优先执行的语句,输出"主线程开始行动",然后在主线程睡眠时间时,子线程开始执行,当执行完:吃多了,要消化20秒时,本应该等待20秒,但是interrupt()打断了子线程,所以就会出现上图的情况。


如果把语句thread.interrupt();删去,就会出现下图的结果:

image.png

相关文章
|
7天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
27 9
|
10天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
7天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
10天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
24 3
|
9天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
10天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
21 1
|
6月前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【2月更文挑战第22天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个主题,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。
58 0
|
6月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
3月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
64 1
|
4月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
94 0

热门文章

最新文章