[JUC学习笔记一] 线程的基本概念、线程的创建、线程的使用

根据尚硅谷的视频做的JUC学习笔记

一、基本概念

  • 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

  • 进程(program):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期

  • 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。

    若一个进程同一时间并行执行多个线程,就是支持多线程的

    线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小

    一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

在这里插入图片描述

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

多线程程序的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

  2. 提高计算机系统CPU的利用率

  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程

程序需要同时执行两个或多个任务。

程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

需要一些后台运行的程序时。


二、线程的创建和使用

1. 线程的创建

方式一:继承于Thread类
  1. 创建一个继承于Thread类的子类

  2. 重写Thread类的run()方法, 将此线程执行的操作声明在run()方法中

  3. 创建Thread类的子类的对象

  4. 通过此对象调用start()方法

    例子:遍历100以内的所有偶数

//1.创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2.重写Thread类的run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0)
                System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类的对象
        MyThread t1 = new MyThread();
        //4.通过此对象调用start()方法:①启动当前线程 ②调用当前线程的run()方法
        t1.start();
        //如下操作仍然是在main线程中执行的
        for (int i = 0; i < 100; i++) {
            if (i%2==0)
                System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}

注意:

① 我们不能通过直接调用run()方法的方式启动线程。

② 不可以还让已经执行start()方法的线程再去start().这样会报IllegalThreadStateException

练习:继承Thread方式,多窗口卖票

public class ThreadDemo2 {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
		//设置线程名称
        w1.setName("窗口 1 ");
        w2.setName("窗口 2 ");
        w3.setName("窗口 3 ");
	 	//启动线程并执行run()方法
        w1.start();
        w2.start();
        w3.start();
    }
}
class Window extends Thread {
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + "卖出了第 " + ticket + " 张票!");
                ticket--;
            }else
                break;
        }
    }
}
方式二:实现Runnable接口
  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread类含参构造器创建线程对象。
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
//1.定义子类,实现Runnable接口。
class MThread implements Runnable {
    //2.子类中重写Runnable接口中的run方法。
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0)
                System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}
public class ThreadTest3 {
    public static void main(String[] args) {
        //3.通过Thread类含参构造器创建线程对象。
        MThread mThread = new MThread();
        //4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
        Thread t1 = new Thread(mThread);
        //5.调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
        t1.start();
    }
}

练习:实现Runnable方式,多窗口卖票

class Window2 implements Runnable {
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第 " + ticket + " 张票!");
                ticket--;
            } else
                break;
        }
    }
}
public class ThreadDemo3 {
    public static void main(String[] args) {
        Window2 w = new Window2();
        
        Thread w1 = new Thread(w);
        Thread w2 = new Thread(w);
        Thread w3 = new Thread(w);
        w1.setName("窗口一");
        w2.setName("窗口二");
        w3.setName("窗口三");
        w1.start();
        w2.start();
        w3.start();
    }
}

两种创建多线程方式的比较

区别:

  • 继承Thread:线程代码存放Thread子类run方法中。
  • 实现Runnable:线程代码存在接口的子类的run方法。

实现方式的好处:

  • 避免了单继承的局限性
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
方式三:实现Callable接口

1.创建一个实现Callable的实现类

2.实现call方法,将此线程需要执行的操作声明在call()方法中

3.创建Callable接口的实现类对象

4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象

5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法

6.获取Callablecall方法的返回值

//创建线程的方式三:实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//1.创建一个实现Callable的实现类
class NumThread implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call()方法中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口的实现类对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
        new Thread(futureTask).start();
        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

与使用Runnable相比,Callable功能更强大些:(1)相比run()方法,可以有返回值(2)方法可以抛出异常(3)支持泛型的返回值(4)需要借助FutureTask类,比如获取返回结果

方式四:使用线程池

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:提高相应速度(减少了创建新线程的时间),降低资源消耗(重复利用线程池中线程,不需要每次都创建),便于线程管理。

创建Thread匿名子类的方式
new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0)
                        System.out.println(Thread.currentThread().getName() + " : " + i);
                }
            }
        }.start();

2. 线程的使用

Thread类的常用方法
  • void start():启动线程,并执行对象的run()方法。

  • run():线程在被调度时执行的操作。

  • String getName():返回线程的名称。

  • void setName(String name):设置该线程名称。

  • static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类。

  • static void yield():释放当前cpu的执行权。线程让步。暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程

    。若队列中没有同优先级的线程,忽略此方法

  • join():当某个程序执行流中调用其他线程的join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止。低优先级的线程也可以获得执行。

  • static void sleep(long millis):令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。抛出InterruptedException异常。

  • stop():强制线程生命期结束,不推荐使用。

  • boolean isAlive():返回boolean,判断线程是否还活着。

线程的调度
  • 调度策略:时间片式、抢占式(高优先级的线程抢占CPU)

  • Java的调度方法:同优先级线程组成先进先出队列(先到先服务),使用时间片策略。对高优先级,使用优先调度的抢占式策略。

线程的优先级

线程创建时继承父线程的优先级。低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用。

getPriority() :返回线程优先值

setPriority(int newPriority) :改变线程的优先级

优先级等级:MAX_PRIORITY:10MIN _PRIORITY:1NORM_PRIORITY:5

线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
  • Java垃圾回收就是一个典型的守护线程。
  • JVM中都是守护线程,当前JVM将退出。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值