JAVA多线程的创建和使用

线程的创建和使用

多线程创建,方式一:继承Thread类

    1. 创建一个继承 Thread 的子类
    2. 重写 Thread 类中的 run() --> 此线程执行的操作申明在run()
    3. 创建 Thread 类的子类的对象
    4. 通过此对象调用 start()
public class ThreadTest extends Thread{

    @Override
    public void run() {
        // 线程休眠 1秒
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String odd = "";
        for (int i = 1; i <= 100; ++ i) {
            if (1 != (i & 1)) {
                odd = odd + i + " ";
            }
        }
        System.out.println("1~100的偶数有:" + odd);
    }
    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest();
        t1.start();
        String even = "";
        for (int i = 1; i <= 100; ++ i) {
            if (1 == (i & 1)) {
                even += i + " ";
            }
        }
        System.out.println("1~100的奇数为:" + even);
    }

}

  • Thread中的常用方法:

      1. start() : 启动当前线程; 调用当前线程的 run()
      2. run() : 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
      3. currentThread() : 静态方法,返回执行当前代码的线程
      4. getName() : 获取当前线程的名字
      5. setName() : 设置当前线程的名字
      6. yield() : 释放当前线程的执行权
      7. sleep(Long milltime) : 令当前活动线程在指定时间内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队。抛出异常InterruptedException异常
      8. join() : 在线程a中调用线程b的 join(),此时线程a就进入到阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
      9. stop() : 强制线程生命周期结束,不推荐使用
      10. isAlive() : 判断当前线程是否存活
  • 线程的调度

    • 调度策略

        1. 时间片
        2. 抢占式:高优先级的线程抢占CPU
    • Java的调度方法

        1. 同优先级线程组成先进先出队列(先到先服务),使用时间策略
        2. 对高优先级,使用优先调度的抢占式策略
    • 线程的优先级

      • 线程的优先级是来改变线程被cpu调度的概率,不是说优先级高就一定被先调度,取决于cpu。
      • MAX_PRIORITY: 10
      • MIN_PRIORITY: 1
      • NORM_PRIORITY: 5
    • 涉及方法

      • getPriority(): 返回线程的优先值

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

      • Thread t1 = new Thread(window, "窗口1");
        
        t1.getPriority();
        t1.setPriority(Thread.MAX_PRIORITY);
        
    • 说明

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

多线程创建,方式二:实现Runnable接口

    1. 创建一个实现了Runnable接口的类
    2. 实现类去实现Runnable中的抽象方法: run()
    3. 创建实现类对象
    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类对象
    5. 通过Thread类的对象调用 start()
package 继承Thread;

public class ThreadTest extends Thread{

    @Override
    public void run() {
        // 线程休眠 1秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String odd = "";
        for (int i = 1; i <= 100; ++ i) {
            if (1 != (i & 1)) {
                odd = odd + i + " ";
            }
        }
        System.out.println("1~100的偶数有:" + odd);
    }
    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest();
        t1.start();
        String even = "";
        for (int i = 1; i <= 100; ++ i) {
            if (1 == (i & 1)) {
                even += i + " ";
            }
        }
        System.out.println("1~100的奇数为:" + even);
    }

}

多个线程共享一个静态资源(无锁)

模拟买票,因为每个线程都有一个虚拟内存,当读到共享资源的时候,将共享资源复制到它虚拟内存内进行处理,然后将结构又赋值给当前的共享资源,这样会导致,当两个线程同时读到相同的票,让后造成重票问题, 当然如果没有出现网络延迟的话,或者处理速度非常快,一般发现不了。

package 实现Runnable;

public class WindowStore {
    public void main(String[] args){
        Window window = new Window();

        new Thread(window).start();
        new Thread(window).start();
        new Thread(window).start();

    }
}

class Window implements Runnable{
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                try {
                    Thread.sleep(100); // 模拟网络延时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 购买了第" + tickets-- + "票");
            }
        }
    }
}


在这里插入图片描述

比较上面两种方式

开发中: 优先选择:实现Runnable接口的方式。

原因: 1. 实现的方式没有类的单继承性的局限性(java中类是不支持多继承,而且当前要使用多线程的类,可能需要继承它的父类,所以用实现方式更方便)。

​ 2.实现的方式更适合用来处理多个线程共享数据的情况(实现方式,是将同一个对象,当成参数传入到多个线程中,保证了数据的唯一,二继承的方式必须声明多个对象)。

线程的分类

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

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
  • 守护线程 是用来服务用户线程的,通过在 start() 方法前调用(thread.setDaemon(true))可以把一个用户线程变成一个守护线程。
  • Java 垃圾回收是一个典型的守护线程。
  • 若Jvm中都是守护线程,当前的jvm将退出。
  • 形象理解:兔死狗烹,鸟尽弓藏。

创建多线程,方式三:实现Callable接口

使用步骤

  • 创建一个实现的Callable的实现类
  • 实现call()方法,将此线程需要执行的操作声明在call()中
  • 创建Callable接口的实现类的对象
  • 将此Callable接口的实现类对象作为参数,传递到FutureTask构造器中,创建FutureTask的对象
  • 将FutureTask对象作为参数,传递到Thread类的构造器中,创建Thread对象,并start()
  • 获取Callablecall方法的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 创建一个实现的**Callable**的实现类
public class NumTread implements Callable {
    // 实现**call**()方法,将此线程需要执行的操作声明在**call**()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) sum += i;
        }
        return sum;
    }

    public static void main(String[] args) {
        // 创建**Callable**接口的实现类的对象
        NumTread numTread = new NumTread();
		//将此**Callable**接口的实现类对象作为参数,传递到FutureTask构造器中,创建**FutureTask**的对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numTread);
		//将FutureTask对象作为参数,传递到**Thread**类的构造器中,创建**Thread**对象,并**start**()
        new Thread(futureTask).start();
        try {
            // get返回值就时Callable实现类的重写的对象。
            //获取**Callable**中**call**方法的返回值
            Integer sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

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

创建多线程,方式四:使用线程池

背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大。

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

优点:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源的消耗(重复利用线程池中的线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize: 设置线程池核心池的大小
    • maximumPoolSize:设置最大的线程数
    • keepAliveTime: 线程池中的线程没有任务时最多保持多少时间后会终止

使用步骤

  • 提供指定数量的线程池

    ExecutorService service = Executors.newFixedThreadPool(10);// 能存十个线程的线程池
    
  • 执行指定的线程操作。需要提供实现的Runnable接口,或提供实现的Callable接口的实现类对象

    // Runnable实现类的对象,使用execute
    service.execute(new NumberThread());
    // Callable实现类的对象使用,submit
    service.submit(Callable callable);
    
  • 关闭线程池

    service.shutdown();
    

测试代码

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

class NumberThread implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}


public class PoolThread {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;

        //体现线程池的管理
        //service1.setCorePoolSize(10);
        
        // 一个线程遍历100以内的偶数,一个遍历一百以内的奇数
        service.execute(new NumberThread()); // 适用于Runnable
        service.execute(new NumberThread1()); // 适用于Runnable
        //service.submit();// 适用于Callable

        service.shutdown();// 关闭线程池
    }
}

创建多线程方式有4种

追求吾之所爱
原文地址:https://www.cnblogs.com/rstz/p/14390978.html