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并临时中止自己的执行,进入阻塞状态 - 死亡
当线程完成它的全部工作或被提前强制地中止或出现异常导致结束