多线程的创建


JVM 内存模型

堆与方法区进程内共享
堆(heap):一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。
类加载器读取类文件后,需要把类、方法、常变量放在堆内存,保存所有引用类型的真实信息,以便执行器执行,堆内存分为三部分:

  • Young 新生区
  • Old 养老区
  • Perm 永久存储区
    方法区和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态变量+编译器编译后的代码等待,虽然JVM规范将方法去描述为堆内存中的一个逻辑部分,但是它还有个别名为Non-Heap非堆,目的就是要和堆分开
    常量池:是方法区的一部分,Class 文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分将类加载后进入方法区的运行时常量池中存放。

方式1: 继承 Thread 类
1.创建一个继承于Thread类的子类
2.重写Thread类的run(),将此线程执行的操作声明在run()中
3.创建Thread类的子类对象
4.通过此对象调用start()

public class ThreadTest{
    public static void main(String[] args) {
        MyThread T1 = new MyThread();
        T1.start();
        for (int i = 0; i < 100; i++) {
            if(i % 2 !=0){
                System.out.println(i+"!!!!!!!!!!!主线程执行中");
            }
        }
    }
}


public class MyThread extends Thread {
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 ==0){
                System.out.println(i);
            }
        }
    }
}


以上代码存在重票,错票问题,即线程不安全。


方式2: 实现 Runnable 接口

  • 创建一个实现了 Runnable 接口的类
  • 实现类去实现 Runnable 中的抽象方法: run()
  • 创建实现类的对象
  • 将此对象作为参数传递到Thread 类的构造器中,创建 Thread 类的对象
  • 通过 Thread 类的对象调用start();
class MyThreads implements Runnable {

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

public class ThreadTests {
    public static void main(String[] args) {
//        创建实现类的对象
        MyThreads mThread = new MyThreads();
//        将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread T1 = new Thread(mThread);
//        通过Thread类的对象调用start();
        T1.start();
    }
}

方式3: 实现 callable 接口

class CreatedThread implements Callable {
  //实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
      //3.创建Callable接口实现类的对象
        CreatedThread createdThread = new CreatedThread();
      //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(createdThread);
      //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) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        System.out.println();

    }
}


方式4: 使用线程池

  • 好处:
  • 1.提高响应速度(减少了创建新线程的时间)
  • 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 3.便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @program: test2
 * @description: 创建线程池
 * @author: Mr.Xiong
 * @create: 2022-11-10 13:45
 **/
class NumThread implements Runnable {

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

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

public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        //        强转后给类进行管理设置属性
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //        设置线程池的属性
        System.out.println(service.getClass());
        service1.setCorePoolSize(15);
        service.execute(new NumThread());//适合runnable
        service.execute(new NumThread2());//适合runnable
        //        service.submit();//适合Callablhe
        service.shutdown();
    }
}

线程的分类

  • 守护线程
  • 用户线程
    唯一区别:判断JVM何时离开。
  • 守护线程是用来服务用户线程的,通过start()前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程.
  • JVM垃圾回收就是一个典型的守护线程.
  • 若JVM中都是守护线程,当前JVM将退出.

线程的生命周期

Java语言使用 Thread 类及其子类的对象表示线程,完整的生命周期通常经历如下5中状态。

  • 新建
    当一个 Thread 类或其子类的对象被声明并创建时
  • 就绪
    被start()后,进入线程队列等待CPU时间片,已经具备运行条件,只是没分配到CPU资源
  • 运行
    就绪的线程被调度并获得CPU资源时,便进入运行状态,run()定义了线程的操作和功能
  • 阻塞
    ,被人为挂起或者执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
  • 死亡
    当线程完成它的全部工作或被提前强制地中止或出现异常导致结束

Author: xt_xiong
转载要求: 如有转载请注明出处 :根据 CC BY 4.0 告知来自 xt_xiong !
评论
 上篇
集合-map接口及多个实现类 集合-map接口及多个实现类
本篇介绍map数据结构,如Hashmap的底层的实现,LinkedHashmap底层实现,Hashmap与Hashtable区别...
2022-11-16
下篇 
抽象类与抽象方法 抽象类与抽象方法
有抽象方法的类,一定是抽象类;抽象类不一定有抽象方法 当子类继承抽象类时,必须要将抽象类中的抽象方法全部实现(或者称为重写),否则子类依然是抽象类 因为子类是继承父类全部内容,所以也就相当于子类中也有了抽象方法,通过重写,可以将抽象方法覆盖。
2022-11-06
  标题