4-5 《Java中多线程重点》——继承Thread实现Runnable死锁线程池Lambda表达式

Posted 美少女降临人世间

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4-5 《Java中多线程重点》——继承Thread实现Runnable死锁线程池Lambda表达式相关的知识,希望对你有一定的参考价值。

文章目录

多线程

多线程:栈空间独立,堆内存共享。

一、线程与进程

  • 进程

正在运行的应用程序:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间

每个进程都有着自己独立的堆、栈且是互不共享的

  • 线程

    • 进程中的一个执行路径(一个应用程序从执行到结束的整个过程),共享一个内存空间,一个进程中可以包含多条线程;
    • 线程之间可以自由切换,并发执行;
    • 一个进程最少有一个线程,线程控制着进程。

二、线程调度

  1. 分时调度

    所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。

  2. 抢占式调度

    优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java使用的为 抢占式调度。

  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

三、同步与异步&并发与并行

同步:排队执行 , 效率低但是安全

异步:同时执行 , 效率高但是数据不安全

并发:指两个任务在同一时间段内都请求运行

处理器只能接收一个任务,把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行

  • 如果用一台电脑先给友人A发个消息,然后立刻再给友人B发消息,然后再跟友人A聊,再跟友人B聊。这就叫并发。

并行:两个任务同一时刻运行,需要多核CPU

  • 比如跟两个朋友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。

四、多线程实现方式

1、继承Thread

步骤:

  1. 定义MyThread类并继承Thread类;
  2. 重写run方法,新的执行路径(通过thread对象的start()启动任务)
/**
 * @author Elvira
 * @date 2020/10/12 16:01
 * @description 继承Thread
 */
public class MyThread extends Thread

    @Override
    public void run() 
        for (int i = 0; i < 10; i++) 
            System.out.println("嘿……"+i);
        
    

  1. 创建子类对象

  2. 调用start(),让线程执行

/**
 * @author Elvira
 * @date 2020/10/12 16:08
 * @description 继承Thread的程序入口
 */
public class ThreadTest 
    public static void main(String[] args) 
        MyThread m = new MyThread();
        m.start();
        for (int i = 0; i < 10; i++) 
            System.out.println("嘻嘻……"
            +i);
        
    

  • 运行结果:可以看到顺序并不统一,这是因为线程在抢占时间片,谁先抢到就是谁的

线程时序图:

注意:运行过程中子线程任务中调用的方法都在子线程中运行

2、实现Runnable

步骤:

  1. 实现Runnable接口;
  2. 实现抽象方法run(),编写线程的任务
/**
 * @author Elvira
 * @date 2020/10/12 16:22
 * @description 实现Runnable
 */
public class MyRunnable implements Runnable

    @Override
    public void run() 
        for (int i = 0; i < 10; i++) 
            System.out.println("哼哼!" + i);
        
     

  1. 创建一个任务对象

  2. 创建一个线程,并给它一个任务

  3. 调用start()方法执行线程

/**
 * @author Elvira
 * @date 2020/10/12 16:30
 * @description 实现Runnable的程序入口
 */
public class RunnableTest 
    public static void main(String[] args) 
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();
        for (int i = 0; i < 10; i++) 
            System.out.println("呵呵?" + i);
        
    

  • 运行结果:

3、实现Runnable和继承Thread比较

继承Thread

  • 优点:直接使用Thread类中的方法,代码简单
  • 弊端:如果已有父类,不可用

实现Runnable接口(更常用)

  • 其优点:

    ​ 1. 通过创建任务,给线程分配任务实现多线程,更适合多个线程同时执行相同任务的情况

    1. 可以避免单继承带来的局限性

    2. 任务和线程分离,提高程序健壮性

    3. 最重要)后续提供的线程池任务,接收Runnable类型任务,不接收Thread类型线程

  • 弊端:不能直接使用Thread中的方法,需先获取线程对象,再得到Thread的方法,代码复杂

4、匿名内部类实现方式

  1. 继承Thread
/**
 * @author Elvira
 * @date 2020/10/12 16:52
 * @description 继承thread的匿名内部类实现方式
 */
public class MyThread0 
    public static void main(String[] args) 
        new Thread() 
            public void run() 
                for (int i = 0; i < 10; i++) 
                    System.out.println("一二三四五" + i);
                
            
        .start();
        for (int i = 0; i < 10; i++) 
            System.out.println("啦啦啦啦啦" + i);
        
    

  • 运行结果:

  1. 实现Runnable
new Thread(new Runnable() 				
    public void run() 			   
        for(int i = 0; i < 5; i++) 	
            System.out.println("aaa" + i);
        
    
).start(); 
for (int i = 0; i < 5; i++) 
    System.out.println("bbb" + i);

  • 运行结果:

五、Thread类

接下来列举API中Thread类最常用的方法:

构造器描述
Thread()分配新的 Thread对象。
Thread(Runnable target)分配新的 Thread对象。
Thread(Runnable target, String name)分配新的 Thread对象。
Thread(String name)分配新的 Thread对象。

常见方法:

变量和类型方法描述
longgetId()返回此Thread的标识符。
StringgetName()返回此线程的名称。
intgetPriority()返回此线程的优先级。
voidsetPriority(int newPriority)更改此线程的优先级。
voidstart()导致此线程开始执行; Java虚拟机调用此线程的run方法。
static voidsleep(long millis)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static voidsleep(long millis, int nanos)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
voidsetDaemon(boolean on)将此线程标记为 daemon线程或用户线程。

特殊字段:控制线程抢到时间片的几率

变量和类型字段描述
static intMAX_PRIORITY线程可以拥有的最大优先级。
static intMIN_PRIORITY线程可以拥有的最低优先级。
static intNORM_PRIORITY分配给线程的默认优先级。

六、设置和获取线程名称

首先需要了解:currentThread() 可以获取当前正在执行的线程对象

  1. 获取线程名称
/**
 * @author Elvira
 * @date 2020/10/12 17:41
 * @description 获取线程名称
 */
public class NameTest 
    public static void main(String[] args) 
        System.out.println(Thread.currentThread().getName());
        //给线程指定一个名称
        new Thread(new MyRunnable(), "Elvira").start();
    

实现类:

public class MyRunnable implements Runnable    
    @Override
    public void run() 
 		System.out.println(Thread.currentThread().getName());
    

  • 运行结果:

如果不指定线程名称,直接给线程一个任务:再加两句

new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();
  • 运行结果:

  1. 设置线程名称

方式一:上述,直接在创建线程任务时指定名称

//给线程指定一个名称
        new Thread(new MyRunnable(), "Elvira").start();

方式二:setName()设置

Thread t = new Thread(new MyRunnable());
t.setName("THREAD");
t.start();
  • 运行结果:

七、线程休眠sleep

sleep是Thread类的静态方法,类名直接调用即可。单位ms。

/**
 * @author Elvira
 * @date 2020/10/12 20:13
 * @description 线程休眠3秒
 */
public class SleepTest 
    public static void main(String[] args) throws InterruptedException 
        for (int i = 0; i < 10; i++) 
            System.out.println(i);
            Thread.sleep(3000);
        
    

  • 运行结果:

    每隔3秒蹦出来一个数字,最终打印了10个数字

线程阻塞:所有较耗时的操作都能称为阻塞。也叫耗时操作。

八、线程的中断

一个线程是一个独立的执行路径,它是否应该结束,由其自身决定

因为线程执行过程会有很多资源需要使用或释放,如果干涉它的结束,很可能导致资源没能来得及释放,一直占用产生无法回收的内存垃圾。

早期有stop()方法可以结束线程,现在已经过时。现在出了新的方法,给线程打标记来控制它的结束。

标记是什么:interrupt

示例:

第一步:先设置main线程和Thread-0线程都执行循环数字1-10的任务

/**
 * @author Elvira
 * @date 2020/10/12 20:28
 * @description 线程中断
 */
public class InterruptTest 
    public static void main(String[] args) 
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for (int i = 1; i <= 5; i++) 
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

实现类:

public class  MyRunnable implements Runnable    
    @Override
    public void run() 
        for (int i = 1; i <= 10; i++) 
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

  • 运行结果:

第二步:给线程t1添加中断标记interrupt()

/**
 * @author Elvira
 * @date 2020/10/12 20:28
 * @description
 */
public class InterruptTest 
    public static void main(String[] args) 
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for (int i = 1; i <= 5; i++) 
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
        t1.interrupt();
    

实现类:

public class  MyRunnable implements Runnable    
    @Override
    public void run() 
        for (int i = 1; i <= 10; i++) 
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                //e.printStackTrace();
                System.out.println("发现了中断标记,还没有停止。");
            
        
    

  • 运行结果:

解释:发现了中断就会捕捉异常进入catch块,程序员决定该如何处理。不进行任何处理程序还会继续执行。此时在catch块中加入return,程序就会中断。

public class  MyRunnable implements Runnable

    @Override
    public void run() 
        for (int i = 1; i <= 10; i++) 
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                //e.printStackTrace();
                System.out.println("发现了中断标记,在此处停止。");
                return;
            
        
    

  • 运行结果:线程结束(”自杀“)

九、守护线程

线程分为守护线程和用户线程

  • 用户线程:当一个进程不包含任何存活的用户线程时,进行结束。

  • 守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡。

设置线程为守护线程:启动之前设置

t1.setDaemon(true);

完整代码:

/**
 * @author Elvira
 * @date 2020/10/13 9:32
 * @description
 */
public class SetDaemonTest 
    public static void main(String[] args) 
        Thread t1 = new Thread(new MyRunnable());
        t1.setDaemon(true);
        t1.start();

        for (int i = 0; i <= 5; i++) 
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        

    

实现类:

public class MyRunnable implements Runnable    
    @Override
    public void run() 
        for (int i = 0; i <= 10; i++) 
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
                return;
            
        
    

  • 运行结果:

十、线程安全问题

举一个生活中的小栗子,模拟买票窗口,创建三个线程,一起卖10张票。此时就会出现线程不安全的问题。

/**
 * @author Elvira
 * @date 2020/10/13 9:49
 * @description
 */
public class ThreadSafeTest 
    public static void main(String[] args) 
        Runnable run = new Ticket();
        //创建三个线程
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    


买票任务:

/**
* 模拟买票窗口
*/
public class Ticket implements Runnable 
    private int count = 10;//票数
    @Override
    public void run() 
        while (count > 0) 
            //开始卖票
            System.out.println(Java——10个关于Java中多线程并发的面试题

最近在研究多线程,浅谈JAVA中多线程的几种实现方式

java中多线程实现方式

java中多线程的Thread类,怎么没有导入任何包就用呢?

Java——15个关于Java中多线程并发的面试题

Java——15个关于Java中多线程并发的面试题