线程基础概念

一、线程基本概念

计算机的组成流程图

进程和线程的区别

进程:正在运行的程序,是操作系统分配系统资源(CPU、内存)的最小单位。

进程更加重量级,操作系统创建和销毁进程需要更多的时间和资源,进程的相互通信更加复杂。

线程更加轻量级,操作系统创建和销毁线程销毁时间和资源更少,同一个进程的线程可以共享内存空间,通信更容易一些。

为什么要多线程?

  • 压榨 CPU 资源,执行高性能运算
  • 同时可以执行多个程序指令,相互的不会影响
  • 服务器可以同时服务多个用户,互不影响

并行和并发

并发:同时执行多个任务,一个 CPU 内核会在多个线程间来回切换执行程序指令,不是真正同时执行。

并行:同时执行多个任务,多个 CPU 内核,一个内核执行一个线程,线程中指令是同时执行的。

同步和异步

同步:多个指令是排队执行的,效率比较低。

异步:多个指令同时执行(借助线程),效率比较高。

二、 线程的实现方法

java 的实现有 4 个:

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 实现 Callable 接口
  4. 通过线程池创建

1. 继承 Thread

1)继承 Thread 类

2)重写 run 方法

3)调用 start 方法

Thread 代码案例:

/**
 * 自定义线程
 */
public class MyThread extends Thread {

    // 重写run方法
    @Override
    public void run() {
        // Thread.currentThread() 当前线程
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "执行了" + i);
        }
    }

    public static void main(String[] args) {
        //创建线程,启动线程
        //启动线程使用start,为什么不使用run? 使用start才能创建新的线程执行,使用run方法就是在主线程中执行
        //多线程是如何执行??? CPU的调度是抢占式,每个线程去抢CPU资源,执行过程中可以被其它线程抢过去
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        //多线程是如何执行???
        myThread.start();
        myThread1.start();
         //主方法中执行的线程是main
        System.out.println(Thread.currentThread().getName() + "执行了");
    }
    
}

面试问题:

1) 启动线程使用start,为什么不使用run?

使用start才能创建新的线程执行,使用run方法就是在主线程中执行。

2) 多线程是如何执行?

CPU的调度是抢占式,,每个线程去抢CPU资源,执行过程中可以被其它线程抢过去

3)start 能否执行多次?

不行,一个线程对象,只能调用一次 start,负责出现异常 IllegalThreadStateException

2. 实现 Runnable 接口

1)实现 Runnable 接口

2)实现 run 方法

3)创建 Thread 对象,传入 Runnable 对象

4)调用 start 启动

Runnable 代码案例:

/**
 * 自定义Runnable对象
 * 更加灵活,继承类后不能继承其他类,实现接口没有任何限制
 * 语言更严格,必须重写run方法,继承类后不强制要求
 */
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "执行了" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
        }
    }

    public static void main(String[] args) {
        //创建Thread对象,同时传入Runnable对象
        Thread thread = new Thread(new MyRunnable());
        //启动线程
        thread.start();
        thread.interrupt();

        //使用lambda表达式实现Runnable
        //for (int j = 0; j < 10; j++) {
        //    new Thread(() -> {
        //        for (int i = 0; i < 100; i++) {
        //            System.out.println(Thread.currentThread().getName() + "执行了" + i);
        //        }
        //    }).start();
        //}
    }
    
}

面试题:

1)继承 Thread 类和实现 Runable 接口的区别

1、继承 Thread 类不能继承其他类,实现 Runnable 接口在继承方面没有限制

2、继承 Thread 类不要求重写 run,实现 Runnable 接口强制要求重写 run

推荐使用 Runnable

3. 实现 Callable 接口

前两种都实施 run 方法,没哟返回值,Callable 接口的方法有返回值

1)实现 Callable 接口

2)实现 call 方法,返回结果、

3)创建 FutureTask 对象,传入 Callable 对象

4)创建 Thread 对象,传入 FutureTask 对象

5)调用 Thread 的 start 方法

6)通过 FutureTask 的 get 方法获得返回结果

Callable 代码案例:

/**
 * 自定义callable对象
 */
public class MyCallable implements Callable<Long> {

    @Override
    public Long call(){
        long sum = 0;
        //长时间运算
        for (int i = 0; i < 1000000L; i++) {
            sum += i;
        }
        System.out.println(Thread.currentThread().getName() + "运算完毕");
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        创建FutureTask对象,传入callable对象
        FutureTask<Long> futureTask = new FutureTask<>(new MyCallable());
//        创建Thread对象,传入FutureTask对象
        Thread thread = new Thread(futureTask);
//        启动线程
        thread.start();
//        获得运算结果
        System.out.println("运算结果是:" + futureTask.get());
    }

}

面试题:

1)实现线程有哪几种方式,有什么 区别?
  1. 继承 Thread 类
  2. 实现 Runnab
  3. 实现 Callablle 接口
  4. 实现 Callable继承 Thread 类,不能继承其他类,实现 Runnable 接口,可以继承其他类,必须强制实现 run 方法
  5. 实现 callable 接口方法可以带返回值,前两种 run 方法都没有返回值

三、线程的生命周期

线程的生命周期:

  • 新建
  • 就绪
  • 运行
  • 阻塞
  • 死亡

线程生命周期流程图

四、线程的主要方法

方法名

说明

start()

启动线程

stop

停止线程,可能导致重要资源无法释放,出现死锁等问题

interrupt

中断线程,可以配合异常处理停止线程

run

执行线程的核心指令

setName(String)

设置线程名字

getName

获得线程名字

sleep( 毫秒)

Thread 的静态方法,让当前线程睡眠一段时间(毫秒)

setPriority(int)

设置优先级,从低到高,1 到 10,优先级高的线程抢占 CPU 几率更高

yield()

放弃占用 cpu 一会,马上回到就绪状态

suspend()

禁用,当前线程挂起(暂停)

resume()

禁用,当前线恢复

setDaemon(boolean)

设置后台线程,默认是 false

join()

合并其他线程,让其他线程先执行完,在执行自身的代码

如何停止线程?

1)stop 禁用,可能会导致重要的资源无法释放,出现死锁等问题

2)等待 run 方法执行结束

3)在 run 方法加入条件,中途停止 run 方法

4)执行 interrupt 方法,进行异常处理的时候停止线程

什么是后台线程?

后台线程是一种特殊线程,这种线程是为其他线程服务的,如果没有存活的其他线程,后台线程自动死亡。

后台线程主要应用场景:GC 垃圾收集器 就是一种后台线程。

后台线程代码案例

/**
 * 后台线程
 */
public class DeamonThreadDemo2 {
    
       /**
     *
     * @param message   設置線程的名字
     * @param sleepTime 設置線程睡眠的時間
     * @param isDaemon  設置是否為後台線程
     * @param num       設置循環次數
     */
    public static void statsThread(String message,int sleepTime,boolean isDaemon,int num){
       Thread thread=new Thread(()->{
            for (int i = 0; i < num; i++) {
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread.currentThread().setName(message+i);
                System.out.println(Thread.currentThread().getName());
            }
        });
        //设置后台问题
        thread.setDaemon(isDaemon);
        //启动线程
        thread.start();
    }

    public static void main(String[] args) {
        statsThread("后台线程",1000,true,10);
        statsThread("bb",500,false,10);
        statsThread("cc",500,false,10);
    }
    
}

join 代码案例

/**
 * 合并线程,让其他线程先执行
 */
public class JoinThreadDemo2 {

    /**
     *
     * @param threadName        設置線程名字
     * @param num               巡檢次數
     * @param sleepTime         睡眠時間
     * @param jsonCondition     條件
     * @param toJson            等待其他線程
     * @return
     */
    public static Thread createAndStartThread(String threadName,int num,int sleepTime,int jsonCondition,Thread toJson){

        Thread thread=new Thread(()->{
                for (int i = 0; i <num; i++) {
                    // 設置睡眠時間
                    try {
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 合并其他线程
                    if(i==jsonCondition&&toJson!=null){
                        try {
                            toJson.join();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 設置線程名稱
                    Thread.currentThread().setName(threadName+i);
                    System.out.println(Thread.currentThread().getName());
                }
            });
        thread.start();
        return thread;
    }

    public static void main(String[] args) {
        Thread thread1 = createAndStartThread("zs", 10, 1000, 5, null);
        Thread thread2 = createAndStartThread("ww", 10, 1000, 5, thread1);
    }
}

setPriority 代码案例

public class ThreadPriorityDemo {

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                
//                if (i == 50) {
//                    当前线程放弃执行
//                    Thread.yield();
//                }
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int j = 0; j < 100; j++) {
                System.out.println(Thread.currentThread().getName() + "--->" + j);
            }
        });
        
//        设置线程的优先级
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();
    }
} 

sleep 和 wait 的区别?

都可以让线程进入阻塞状态

区别:

  • 调用对象不同:sleep 是线程调用的,wait 是锁对象(object)调用的
  • 释放锁不同:sleep 不会让线程释放锁,wait 会让线程去释放锁
  • 唤醒机制不同:sleep 只能等睡眠的时间结束,wait 可以等时间结束 也 可以通过 notify 方法唤醒

作业:

面试题:

  • 进程和线程的关系
  • 创建线程的几种方式 ,以及区别
  • 启动线程使用start还是run
  • 多线程的执行是什么顺序
  • sleep和wait区别
  • 线程的生命周期

上机题:

  1. 编写一个程序,模拟一个电影院售票系统。有10个售票窗口,每个窗口可以售卖一张电影票。创建10个线程,每个线程代表一个售票窗口,模拟售票过程。确保多个线程同时访问编写一个程序,模拟一个电影院售票系统。有10个售票窗口,每个窗口可以售卖一张电影票。创建10个线程,每个线程代表一个售票窗口,模拟售票过程。确保多个线程同时访问共享资源时不会出现冲突。
  2. 编写一个程序,创建两个线程,一个线程每隔1秒打印一次"ping",另一个线程每隔2秒打印一次"pong",打印10次后程序结束。
  3. 启动扫描文件的线程,用来扫描某个目录下所有的文本文件(txt\html\java\css\js\json),并输出文本内容
  4. 模拟龟兔赛跑,跑10000米,乌龟跑10米睡100毫秒,兔子跑50米睡500毫秒,刺猬作为啦啦队一直在喊加油,比赛完后自动回家

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值