《JavaSE-第二十章》之线程的创建与Thread类

简介: 《JavaSE-第二十章》之线程的创建与Thread类

文章目录

什么是进程?

什么是线程?

为什么需要线程?

基本的线程机制

创建线程

1.实现 Runnable 接口

2.继承 Thread 类

3.其他变形

Thread

常见构造方法

1. Thread()

2. Thread(Runnable target)

3. Thread(String name)

4. Thread(Runnable target, String name)

常见属性

常用方法

启动一个线程start()

休眠

优先级

让步

后台线程

线程等待

什么是进程?

我们平时安装的程序都放在硬盘上,当我们在windows上双击可执行程序(exe)时才会将其运行起来。本质上就是将这个程序加载到内存中,然后CPU才会对该程序的数据和代码进行读取并逐行的执行,一旦将程加载到内存后,此时程序从静态的趟在硬盘上到动态的运行在内存中,我们就将在内存中的程序,称之为进程。

什么是线程?

在一个程序里的一个执行路线就叫做线程。更准确的定义是:线程是进程内部的执行序列。这就好比整个银行就是用一个进程,里面每个柜台的工作人员就是一个线程。

为什么需要线程?

  1. 1.创建和销毁线程比创建和销毁进程快
  2. 2.调度线程比调度进程快
  3. 3.为了让一个程序运行得更快,可以将程序划分成多个任务,然后去充分利用多核CPU

基本的线程机制

并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务(也被称为子任务)中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,但是你的程序使得每个任务都好像有其自己的CPU一样。其底层机制是切分CPU时间,但通常你不需要考虑它。

创建线程

1.实现 Runnable 接口

线程可以驱动任务,因此需要一个描述任务的方式,这个由Runnable接口提供,想定义任务,只需要实现Runnable接口并重写run()方法。

public class LiftOff implements Runnable{  
    protected int countDown  = 10;  
    private static  int taskCount=0;  
    private final int id = taskCount++;  
    public LiftOff(int countDown) {  
        this.countDown = countDown;  
    }  
    public String status() {  
        return "#"+id+"("  
                +(countDown>0?countDown:"Liftoff!")+")";  
    }  
    public LiftOff() {  
    }  
    @Override  
    public void run() {  
        while (countDown-- >0){  
            System.out.println(status());  
            //建议CPU从一个线程切换到另一线程
            Thread.yield();  
        }  
    }  
    public static void main(String[] args) {  
        LiftOff l = new LiftOff();  
        l.run();//启动线程 
    }  
}

2.继承 Thread 类

public class MyThread extends Thread{  
    protected int countDown  = 10;  
    private static  int taskCount=0;  
    private final int id = taskCount++;  
    public MyThread(int countDown) {  
        this.countDown = countDown;  
    }  
    public String status() {  
        return "#"+id+"("  
                +(countDown>0?countDown:"MyThreadOff!")+")";  
    }  
    public MyThread() {  
    }  
    @Override  
    public void run() {  
        while (countDown-- >0){  
            System.out.println(status());  
            Thread.yield();  
        }  
    }  
    public static void main(String[] args) {  
        MyThread l = new MyThread();  
        l.start();  // 线程开始运行
    }  
}

3.其他变形

  • 匿名内部类创建 Thread 子类对象
Thread t = new Thrad(){  
    @Override  
    public void run() {  
        System.out.println("Hello World!");  
    }  
};  
t.start();
  • 匿名内部类创建 Runnable 子类对象
Thread t = new Thread(new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello World");  
    }  
});  
t.start();
  • lambda 表达式创建 Runnable 子类对象
Thread t = new Thread(() -> System.out.println("Hello World"));  
t.start();  
Thread t2  = new Thread(() -> System.out.println("Hello World"))  
t2.start();

多线程的优势-增加运行速度

我们使用多线程和单线程分别来完成a和b两个变量都自增20亿次,并统计所需时间。

public class ThreadAdvantage {  
    private static final long count = 10_0000_0000;  
    public static void main(String[] args) throws InterruptedException {  
        //并发  
        concurrency();  
        //串行  
        serial();  
    }  
    private static void concurrency() throws InterruptedException {  
        long start = System.currentTimeMillis();  
        Thread t = new Thread(() -> {  
            int a = 0;  
            for (int i = 0; i < count; i++) {  
                a--;  
            }  
        });  
        Thread t2 = new Thread(() -> {  
            int b = 0;  
            for (int i = 0; i < count; i++) {  
                b--;  
            }  
        });  
        t.start();  
        t2.start();  
        t.join();  
        t2.join();  
        long end = System.currentTimeMillis();  
        System.out.println("串行:毫秒:" +(end - start) + "ms");  
    }  
    private static void serial() {  
        long start = System.currentTimeMillis();  
        int a = 0;  
        for (int i = 0; i < count; i++) {  
            a--;  
        }  
        int b = 0;  
        for (int i = 0; i < count; i++) {  
            b--;  
        }  
        long end = System.currentTimeMillis();  
        System.out.println("串行:毫秒:" +(end - start) + "ms");  
    }  
    }

串行和并发所需时间

Thread

常见构造方法

1. Thread()
public class Demo {  
    public static void main(String[] args) {  
        Thread t = new Thread();  
        t.start();  
    }  
}

该构造方法创建了一个Thread对象,并没有添加任务,所以启动后啥也没做就正常退出了。

2. Thread(Runnable target)
public class Demo {  
    public static void main(String[] args) {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                System.out.println("Hello World");  
            }  
        });  
        t.start();  
    }  
}

控制台输出

该构造方法使用 Runnable 对象创建线程对象

3. Thread(String name)
public class Demo {  
    public static void main(String[] args) {  
        Thread t = new Thread("thread1"){  
            @Override  
            public void run() {  
              //得到这个线程的名字
                System.out.println(Thread.currentThread().getName());  
            }  
        };  
        t.start();  
    }  
}

控制台输出

该构造方法创建线程对象,并命名。

4. Thread(Runnable target, String name)
public class Demo {  
    public static void main(String[] args) {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                System.out.println(Thread.currentThread().getName());  
            }  
        },"thread1");  
        t.start();  
    }  
}
public class Demo {  
    public static void main(String[] args) {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                System.out.println(Thread.currentThread().getName());  
            }  
        },"thread1");  
        t.start();  
    }  
}

运行结果:

常见属性

屏幕截图 2023-08-08 153332.png

示例代码

public class ThreadDemo {  
    public static void main(String[] args) {  
        Thread thread = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                try {  
                    System.out.println(Thread.currentThread().getName() + ": 我还活着");  
                    Thread.sleep(1 * 1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");  
        });  
        System.out.println(Thread.currentThread().getName()  
                + ": ID: " + thread.getId());  
        System.out.println(Thread.currentThread().getName()  
                + ": 名称: " + thread.getName());  
        System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());  
        System.out.println(Thread.currentThread().getName()  
                + ": 优先级: " + thread.getPriority());  
        System.out.println(Thread.currentThread().getName()  
                + ": 后台线程: " + thread.isDaemon());  
        System.out.println(Thread.currentThread().getName()  
                + ": 活着: " + thread.isAlive());  
        System.out.println(Thread.currentThread().getName()  
                + ": 被中断: " + thread.isInterrupted());  
        thread.start();  
        while (thread.isAlive()) {  
        }        System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());  
    }  
}

运行结果:

main: ID: 20

main: 名称: Thread-0

main: 状态: NEW

main: 优先级: 5

main: 后台线程: false

main: 活着: false

main: 被中断: false

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我还活着

Thread-0: 我即将死去

main: 状态: TERMINATED

Process finished with exit code 0

从任务中产生返回值

Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引入的Callabel是一种具有类型参数的泛型,它的类型参数表示的是从方法call()(而不是run())中返回的值,并且必须使用ExecutorService.submit()方法调用它,下面是一个简单示例:

import java.util.concurrent.Callable;
public class TaskWithResult implements Callable<String> {  
    private int id;  
    public TaskWithResult(int id){  
        this.id = id;  
    }  
    @Override  
    public String call() throws Exception {  
        return "result of TaskWithResult"+id;  
    }  
}

测试代码

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Future;  
public class CallableDemo {  
    public static void main(String[] args) {  
        ExecutorService executorService = Executors.newSingleThreadExecutor();  
        List<Future> list = new ArrayList<Future>();  
        for (int i = 0; i < 10; i++) {  
            //executorService.submit()会返回Future对象  
            list.add(executorService.submit(new TaskWithResult(i) ));  
        }  
        for (Future<String> future : list) {  
            try {  
                System.out.println(future.get());  
                System.out.println(future.isDone());  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            } catch (ExecutionException e) {  
                throw new RuntimeException(e);  
            }finally {  
                executorService.shutdown();  
            }  
        }  
    }  
}

运行结果:

result of TaskWithResult0

true

result of TaskWithResult1

true

result of TaskWithResult2

true

result of TaskWithResult3

true

result of TaskWithResult4

true

result of TaskWithResult5

true

result of TaskWithResult6

true

result of TaskWithResult7

true

result of TaskWithResult8

true

result of TaskWithResult9

true

Process finished with exit code 0

常用方法

启动一个线程start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程

就开始运行了。覆盖的run()方法是提供给线程要干的活,此时的线程对象是处于就绪态,也就是分配好了系统资源,并执行了初始化,但是只有调用start()方法时该线程才是真正创建出来。

示例代码

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },"thread2");
        t.start();
        t.run();
    }
}

运行结果:

可以看出调用run()方法实际上是main线程 在调用,只有调用start()方法时t线程才会被真正的创建。

休眠

影响任务行为的一种简单方法是调用sleep(),这将使任务中止执行给定的时间。

import java.util.concurrent.TimeUnit;  
public class SleepingTask implements Runnable {  
    private static long count = 10;  
    @Override  
    public void run() {  
        while (count-- > 0) {  
            try {  
            //JavaSE5引入了更加通俗的sleep()版本作为TimeUnit类的一部分。
                //Thread.sleep(1000);  
                TimeUnit.MILLISECONDS.sleep(100);  
                System.out.println("线程名字:" + Thread.currentThread().getName() + " 线程Id:" + Thread.currentThread().getId());  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
    public static void main(String[] args) {  
        for (int i = 0; i < 5; i++) {  
            Thread t = new Thread(new SleepingTask());  
            t.start();  
        }  
    }  
}

对sleep()的调用可以抛出InterruptedException异常,并且你可以看到,它在run()中被捕获。因为异常不能跨线程传播回main(),所以你必须在本地处理所有在任务内部产生的异常。

优先级

线程的优先级是将告诉调度器这个线程重不重要,重要的话调度器会倾向于优先权最高的线程执行。但是.这个不意味 着优先级低的就得不到执行,只不过就是优先级低的被执行的频率比较低。这个所谓的优先级也不过是语言级别给操作系统的建议,操作系统听不听这还难整~~~ 。在大多数时间里,所有的线程都应该以默认的优先级执行,尝试去通过优先级去影响线程的执行是比较鸡肋的。

下面是一个演示优先级等级的示例,我们可以通过getpriority()来读取现有线程的优先级,并且在任何时刻都可以通过setPriority()来修改优先级。

public class SimpleProiorities implements Runnable {  
    private int countDown = 5;  
    //避免编译器优化
    private volatile double d;  
    private int priority;  
    public SimpleProiorities(int priority) {  
        this.priority = priority;  
    }  
    @Override  
    public String toString() {  
        return Thread.currentThread() + ":" + countDown;  
    }  
    @Override  
    public void run() {  
        Thread.currentThread().setPriority(priority);  
        while (true) {  
            for (int i = 1; i < 100000; i++) {  
                d = (Math.PI + Math.E) / (double) i;  
                if (i % 1000 == 0) {  
                    Thread.yield();  
                }  
            }  
            System.out.println(this);  
            if (--countDown == 0) {  
                return;  
            }  
        }  
    }  
    public static void main(String[] args) {  
        for (int i = 0; i < 5; i++) {  
          //最小优先级是1
            Thread t = new Thread(new SimpleProiorities(Thread.MIN_PRIORITY));  
            //最大的优先级是10
            Thread t1 = new Thread(new SimpleProiorities(Thread.MAX_PRIORITY));  
            t.start();  
            t1.start();  
        }  
    }  
}

运行结果:

可以看出,优先级高的运行的频率要高一些。在Java中一共有10个优先级,即从1-10。但它与多数操作系统都不能映射得很好。比如,Windows有7个优先级且不是固定的,所以这种映射关系也是不确定的。Sun的Solaris有231个优先级。唯一可移植的方法是当调整优先级的时候,只使用MAX_PRIORITY(10)、NORM_PRIORITY(5)和MIN_PRIORITY(1)三种级别。

让步

当一个线程的主要任务基本完成时,就可以给线程调度机制一个暗示:该线程的活干的差不多了.可以让别的线程使用CPU。这个暗示通过yield()方法作出,不过这也是个鸡肋的操作,因为无法保证百分之百调度机制就会采纳。

示例代码

public class YieLdDemo {  
    private static int count1 = 0;  
    private static int count2 = 0;  
    public static void main(String[] args) {  
        Thread t1 = new Thread(() -> {  
            while (true) {  
                System.out.println("张三:"+count1++);  
                //Thread.yield();  
            }  
        },"t1");  
        Thread t2 = new Thread(() -> {  
            while (true) {  
                System.out.println("王五:"+count2++);  
            }  
        },"t2");  
        t1.start();  
        t2.start();  
    }  
}

t1不使用Thread.yield()方法的运行结果:

t1使用Thread.yield()方法的运行结果

可以看出:

  1. 1.不使用yidId的时候,张三和王五基本五五开
  2. 2.使用yieId时,王五的数量远远大于张三。
    结论:
    yield 不改变线程的状态, 但是会重新排队
后台线程

后台线程是程序运行时在后台提供的一种通用服务的线程,并且这种线程并不属于程序必不可少的部分。因此,当所有的非后台线程退出时,程序就会终止,并且会杀死进程中的所有非后台线程。比如我们常用的main就是一个非后台线程,其中后台线程退出时不会影响进程的退出,前台线程退出时会影响进程的退出。

示例代码

import java.util.concurrent.TimeUnit;  
public class SimpleDaemons implements  Runnable{  
    @Override  
    public void run() {  
        while (true) {  
            try {  
                TimeUnit.MILLISECONDS.sleep(100);  
                System.out.println(Thread.currentThread()+" " + this);  
            } catch (InterruptedException e) {  
                System.out.println(e.getMessage());  
            }  
        }  
    }  
    public static void main(String[] args) throws InterruptedException {  
        //main为非后台线程  
        int i=0;  
        for (i=0;i<100;i++) {  
            Thread th = new Thread(new SimpleDaemons());  
            th.setDaemon(true);//设置为后台线程  
            th.start();  
        }  
        System.out.println("All daemons started");  
        Thread.sleep(100);  
    }  
}

运行结果:

All daemons started

Thread[Thread-38,5,main] org.example.thread.SimpleDaemons@1eec3130

Thread[Thread-23,5,main] org.example.thread.SimpleDaemons@63ee6746

Thread[Thread-17,5,main] org.example.thread.SimpleDaemons@22867941

Thread[Thread-22,5,main] org.example.thread.SimpleDaemons@2de94878

Thread[Thread-62,5,main] org.example.thread.SimpleDaemons@63158364

Thread[Thread-5,5,main] org.example.thread.SimpleDaemons@7f25713

Thread[Thread-15,5,main] org.example.thread.SimpleDaemons@72139b8

Thread[Thread-16,5,main] org.example.thread.SimpleDaemons@5c7b595c

Thread[Thread-19,5,main] org.example.thread.SimpleDaemons@8242116

Thread[Thread-20,5,main] org.example.thread.SimpleDaemons@5658172e

Thread[Thread-25,5,main] org.example.thread.SimpleDaemons@31f176f0

Thread[Thread-30,5,main] org.example.thread.SimpleDaemons@234fbf7

Thread[Thread-32,5,main] org.example.thread.SimpleDaemons@365fee59

Thread[Thread-27,5,main] org.example.thread.SimpleDaemons@2f524b71

Process finished with exit code 0

线程等待

线程A可以在线程B上调join()方法,结果就是B线程挂起等待A线程执行结束才能继续执行。

示例代码

public class JoinExample {  
    public static void main(String[] args) {  
        Thread thread1 = new Thread(() -> {  
            for (int i = 1; i <= 5; i++) {  
                System.out.println("Thread 1 - " + i);  
            }  
        });  
        Thread thread2 = new Thread(() -> {  
            try {  
                thread1.join(); // 等待Thread 1结束  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            for (int i = 1; i <= 5; i++) {  
                System.out.println("Thread 2 - " + i);  
            }  
        });  
        thread1.start();  
        thread2.start();  
    }  
}

运行结果

相关文章
|
30天前
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
Lua的面向对象编程、协同线程与协同函数的概念和使用,以及Lua文件I/O操作的基本方法。
21 4
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
|
10天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
12 3
|
10天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
24 2
|
10天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
22 2
|
10天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
22 1
|
30天前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
31 0
|
26天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
38 1
C++ 多线程之初识多线程
|
10天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
10 2
|
10天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
22 1
|
10天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
18 1