掌握Java基础:多线程编程技巧

发表时间: 2023-11-19 18:59

一、线程与进程

进程:是指一个内存中运行的程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程。

线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。【java默认有两个线程:main、GC】

进程与线程的区别:

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。线程就是CPU调度和执行的单位。

多线程(multithreading)【百科】:是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理“。

二、线程的实现方式

1、继承Thread类

1.1、实现步骤

  1. 继承Thread类的子类,并重写该类的run()方法(该run()方法的方法体就代表了线程需要完成的任务,因此run()方法称为线程执行体)
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

1.2、实现案例

/** * 继承Thread 类 * 注意: 线程开启不一定立即执行,由CPU调度执行 */public class TestThread extends Thread{     @Override    public void run() {        // run的方法体        for (int i = 0; i < 2000; i++) {            System.out.println("我在刷抖音!" + i);        }    }     public static void main(String[] args) {        TestThread thread = new TestThread();        thread.start();         for (int i = 0; i < 2000; i++) {            System.out.println("我在上厕所!" + i);        }    }}

1.3、扩展:

1.3.1、构造方法

  1. public Thread()
    分配一个新的线程对象。
  2. public Thread(String name)
    分配一个指定名字的新的线程对象
  3. public Thread(Runnable target)
    分配一个带有指定目标新的线程对象
  4. public Thread(Runnable target,String name)
    分配一个带有指定目标新的线程对象并指定名字

1.3.2、常用方法

  1. public String getName() :获取当前线程名称。
  2. public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
  3. public void run() :此线程要执行的任务在此处定义代码。
  4. public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  5. public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

2、实现Runnable接口

2.1、优势:

  1. 避免单继承的局限性
    一个类继承了Thread类就不能继承其他的类
    一个类实现了Runnable接口,还可以继续继承别的类,实现其他的接口
  2. 增强了程序的扩展性,降低程序的耦合度
    使用Runnable接口把设置线程任务和开启线程相分离
    实现类当中,重写run方法,设置线程任务
    创建Thread类对象,调用 start方法,开启新线程

2.2、实现步骤

  1. 创建一个RunnableImpl类实现Runnable接口
  2. 重写Runnable接口中的run方法,设置线程任务
  3. 创建Runnable接口的实现类RunnableImpl的对象
  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类RunnableImpl的对象
  5. 调用Thread类中的start方法,开启新的线程,执行run方法

2.3、实现实例:

/** * 实现Runnable 接口 */public class TestRunnable implements Runnable {    @Override    public void run() {        // run的方法体        for (int i = 0; i < 2000; i++) {            System.out.println("我在刷抖音!" + i);        }    }     public static void main(String[] args) {        Thread thread = new Thread(new TestRunnable());        thread.start();         for (int i = 0; i < 2000; i++) {            System.out.println("我在上厕所!" + i);        }    }}

2.4、构造方法

  1. Thread(Runnable target) 分配新的 Thread对象
  2. Thread(Runnable target, String name) 分配新的 Thread对象【推荐该方法,因为可以自定义线程名】

3、实现Callable 接口

3.1、实现步骤

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1); //1为开辟的线程池中线程的数量
  5. 提交执行Future result1 = ser.submit(t1); //线程
  6. 获取结果:boolean r1 = resut1.get() //指定线程的返回结果
  7. 关闭服务:ser.shutdown()
/** * 实现Callable 接口 * * 好处: 1.可以定义返回值类型 2、可以抛出异常 */public class TestCallable implements Callable<Boolean> {     String name = null;     public TestCallable(String name){        this.name = name;    }     @Override    public Boolean call() throws Exception {        // run的方法体        for (int i = 1; i <= 20; i++) {            System.out.println(name + i);        }         return true;    }     public static void main(String[] args) throws ExecutionException, InterruptedException {        // 1.创建线程        TestCallable t1 = new TestCallable("睡觉!");        TestCallable t2 = new TestCallable("水饺");         // 2.创建执行服务        ExecutorService ser = Executors.newFixedThreadPool(2);         // 3.提交执行        Future<Boolean> r1 = ser.submit(t1);        Future<Boolean> r2 = ser.submit(t2);         // 4.获取结果        Boolean res1 = r1.get();        Boolean res2 = r2.get();         System.out.println(res1);        System.out.println(res2);         // 5.关闭服务        ser.shutdown();    }}

4、线程池

后续章节补充

三、线程方法

1、守护线程

线程分为守护线程用户线程,守护线程的作用是为其他线程运行提供便利服务,用户线程如果全部退出执行,守护线程也会随之关闭,守护线程最经典的 例子GC(垃圾回收器)。

/** * 守护线程*/public class TestDaemon {    public static void main(String[] args) {        Mom mom = new Mom();        Thread thread = new Thread(mom);        thread.setDaemon(true); // 开启守护线程        thread.start();         new Thread(new Me()).start();    }}  class Mom implements Runnable {    @Override    public void run() {        while (true){            System.out.println("妈妈监视我写作业!!");        }    }} class Me implements Runnable {     @Override    public void run() {        for (int i = 0; i < 300; i++) {            System.out.println("我在写作业!!");        }        System.out.println("===》我作业写完了!!");    }}

2、线程强制插入JOIN()

JOIN合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。

/** * 线程插队 JOIN() */public class TestJoin implements Runnable{    @Override    public void run() {        for (int i = 0; i < 200; i++) {            System.out.println("Vip来了....." + i);        }    }     public static void main(String[] args) throws InterruptedException {        TestJoin testJoin = new TestJoin();        Thread thread = new Thread(testJoin);        thread.start();         for (int i = 0; i < 1000; i++) {            if (i == 500){                thread.join(); // 插队            }            System.out.println("main" +i);        }    }}

3、线程优先级

线程的优先级越高意味着获得调度的概率就高,但并不代表优先级低的线程就不会被调度,这都是看CPU心情的!!

/** * 线程优先级 * 线程默认的优先级 5  最大是 10  最小 1 * 线程设置优先级,要在线程启动前设置 */public class TestPriority implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "====>" + Thread.currentThread().getPriority());    }     public static void main(String[] args) {        // 主线程的默认优先级        System.out.println(Thread.currentThread().getName() + "====>" + Thread.currentThread().getPriority());         TestPriority testPriority = new TestPriority();        Thread t1 = new Thread(testPriority, "t1");        Thread t2 = new Thread(testPriority, "t2");        Thread t3 = new Thread(testPriority, "t3");         // 设置优先级3        t1.setPriority(3);        t1.start();         // 设置最大优先级        t2.setPriority(Thread.MAX_PRIORITY);        t2.start();         // 设置最小优先级        t3.setPriority(Thread.MIN_PRIORITY);        t3.start();    }}

4、线程休眠

  1. sleep(时间)指定当前线程阻塞的毫秒数;
  2. 每个对象都有一个锁,sleep不会释放锁;
  3. sleep存在异常InterruptedException;
  4. sleep可以模拟网络延、倒计时;

案例:

/** * 线程睡眠 : 更容易发现多线程下方法存在的问题 (放大问题的发生性) */public class TestSleep implements Runnable{     int ticket = 10;     @Override    public void run() {        while (true){            if (ticket <= 0 ){                break;            }             try {                Thread.sleep(200);            } catch (InterruptedException e) {                e.printStackTrace();            }             System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "票");        }    }     public static void main(String[] args) {        TestSleep ticket = new TestSleep();         // 模拟三条线程同时抢票        new Thread(ticket,"小明").start();        new Thread(ticket,"小强").start();        new Thread(ticket,"黄牛").start();    }}

四、线程的五种状态

线程的五种状态

Java中对于线程总共设定了5个状态,分别为:新建状态、就绪状态、运行状态、阻塞状态、死亡状态

  • 新建状态(New):顾名思义就是我们通过new Thread() 创建了线程,但是还并未启动线程。
  • 就绪状态(Runnable):当其他线程调用start 方法启动该线程的时候,线程首先会进入准备就绪状态也被称为“可运行状态”,随时等待线程调度程序获取CPU的执行时间(即CPU时间片)。
  • 运行状态(Running):线程调度程序一旦获取到了CPU的执行时间线程就进入运行状态并执行线程的程序代码。
  • 阻塞状态(Blocked):阻塞状态是指线程因为某种原因放弃了CPU使用权,让出了CPU的执行时间。直到线程进入“可运行状态”,才有机会再次获得CPU执行时间 转到“运行状态”。
  • 死亡状态(Dead):当线程执行代码运行完之后或者因为异常退出了run方法,那么该线程都会结束其生命周期。

导致线程阻塞主要有三种情况:

  • ①无限等待:当调用了没有时间参数的Object.wait()、Thread.join()、LockSupport.park()等方法,当前线程就会处于无限等待状态,这种等待需要其他线程显示的唤醒才能重新获取CPU执行时间进入运行状态,例如:调用Object.notify()可以唤醒调用Object.wait()阻塞的线程。
  • ②限时等待:当调用了Thread.sleep()、Object.wait(timeout)、Thread.join(millis)、LockSupport.parkNanos(nanos)、LockSupport.parkUnit(deadline)等方法,当前线程也会处于等待状态,但是无须等待被其他线程显示的唤醒,在一定时间后它们会由系统自动唤醒。
  • ③同步等待:运行的线程在获取对象的synchronized同步锁时,若该同步锁被别的线程占用则获取失败,JVM会把该线程放入锁池(lock pool)中,它会进入同步阻塞状态。等占用同步锁的线程释放了同步锁之后,线程就会再次尝试获取同步锁,如果获取成功则进入运行状态。