【06章多线程】Java语言的重头戏

❤写在前面
❤博客主页:努力的小鳴人
❤系列专栏:JavaSE超详总结😋
❤欢迎小伙伴们,点赞👍关注🔎收藏🍔一起学习!
❤如有错误的地方,还请小伙伴们指正!🌹

🔥系列传送门
【05章Java异常处理】一篇短文教你玩转Java异常处理【热榜】
【附章2Java面向对象编程】盘点关键字详解 this、super、final【热榜】
【04章Java面向对象编程(下)】面向对象编程的必杀技【热榜】
【04章Java面向对象编程(中)】解密 继承性和多态性【热榜】
【04章Java面向对象编程(上)】万事万物皆对象的初体验【全站热榜第二】
【附章1Java数组中常见算法】图文并茂详解十大排序算法让您回味无穷
【03章Java数组】程序员必看数组详解
【02章Java基本语法】详细讲解让你重新认识Java基本语法和流程流程控制
【01章Java语言概述】Java学了很久了吧,快回来熟悉熟悉吧(详细)


一、基本概念

🔥程序

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

🔥进程

定义:是程序的一次执行过程,或是正在运行的一个程序
它自身的产生、存在和消亡的过程为生命周期

🔥线程

  1. 定义:进程可进一步细化为线程,是一个程序内部的一条执行路径
  2. 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器
  3. 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象

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

🔥并行与并发

并行多个CPU同时执行多个任务,比如:多个人同时做不同的事。
并发一个CPU同时执行多个任务,比如:多个人做同一件事

二、线程的创建和使用

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现

🔥方式一:继承Thread类

  1. 定义子类继承Thread类
  2. 子类中重写Thread类中的run方法
  3. 创建Thread子类对象,即创建了线程对象
  4. 调用线程对象start方法:启动线程,调用run方法
    小试牛刀:
class MyThread extends Thread{
	public MyThread(){
	super();
	}
	public void run(){
		for(int i = 0;i<100;i++){
			System.out.println("子线程:" + i);
		}
	}
}
public class TestThread{
	public static void main(String[] args){
		//1.创建线程
		MyThread m = new MyThread();
		//2.启动线程,并调用当前线程的run()方法
		m.start;
	}
}

🔥方式二:实现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 ThreadTest {
    public static void main(String[] args) {
        //3.创建实现类的对象
        MThread mThread = new MThread();
        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        t1.setName("线程一");
        //5。通过Thread类的对象调用start() : ①启动线程 ②调用当前线程的run()
        t1.start();

        //再启动一个线程,遍历100以内的偶数
        Thread t2 = new Thread(mThread);
        t2.setName("线程二");
        t2.start();
    }
}

🎁注:想要启动多线程,必须调用start方法

🔥JDK5.0新增线程创建方式

👌方式三:实现Callable接口

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

  1. 步骤:
  • 创建一个实现callable实现类
  • 实现call方法,将次线程需要执行的操作声明在call()
  • 创建Callable接口实现类的对象
  • 将此Callable接口实现类的对象作为传递到FultureTast构造器中,创建FutureTast的对象
  • 将FutureTast的对象作为参数传递到Thread类的构造器中,创建Thread对象,并start()
  1. 上代码:
//1.创建一个实现callable实现类
class NumThread implements Callable {
    //2.实现call方法,将次线程需要执行的操作系统费声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; 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接口实现类的对象作为传递到FultureTast构造器中,创建FutureTast的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTast的对象作为参数传递到Thread类的构造器中,创建Thread对象,并start()
        new Thread(futureTask).start();

        try {
            //get()返回值即为FutureTest构造器参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

👌方式四:使用线程池

  1. 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用
  2. 步骤:
  • 提供指定线程数量的线程池
  • 执行指定的线程操作,需要提供实现Runnable或者Callable接口实现类的对象
  • 关闭线程池
  1. 上代码
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 ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        //ExecutorService是接口,service是其实现类的对象
        ExecutorService service = Executors.newFixedThreadPool(10);

        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
        //设置线程池属性
//        System.out.println(service.getClass());找出ExecutorService的实现类
//        service1.setCorePoolSize(15);//核心池大小
//        service1.setKeepAliveTime();//线程没有任务时最多保持多长时间后会终止
//        service1.setMaximumPoolSize(100);//最大线程数

        //2.执行指定的线程操作,需要提供实现Runnable或者Callable接口实现类的对象
        service.execute(new NumberThread());//适用于Runnable
        service.execute(new NumberThread1());//适用于Runnable
//        service.submit(Callable callable);//适用于Callable

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

三、线程的生命周期

一个完整的生命周期中通常经历如下的五种状态:新建、就绪、运行、阻塞、死亡
知识点尽在图中
在这里插入图片描述
对图中的解析
释放锁的操作
●当前线程的同步方法、同步代码块执行结束
●当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行
●当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
●当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁的操作
●线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
●线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

四、线程的同步

Java对于多线程的安全问题提供了专业的解决方式:同步机制

🔥Synchronized

  1. 同步代码块
    synchronized (对象){
    // 需要被同步的代码;
    }
  2. synchronized还可以放在方法声明中,表示整个方法为同步方法。
    如:
    public synchronized void show (String name){
    ….
    }

🔥锁

  1. 定义:通过显式定义同步锁对象来实现同步。使用Lock对象充当
    java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
class A{
		private final ReentrantLock lock = new ReenTrantLock();
		public void m(){
		lock.lock();
		try{
			//保证线程安全的代码;
		}
		finally{
				lock.unlock();
		}
	}
}
  1. synchronized的锁是什么?
    ●任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)
    同步方法的锁:静态方法(类名.class)、非静态方法(this)
    同步代码块:自己指定,很多时候也是指定为this或类名.class

在线程的生命周期图下解析中也有分析

👌死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁,出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

代码如下:

public class DeadLockTest {
		public static void main(String[] args) {
			final StringBuffer s1 = new StringBuffer();
			final StringBuffer s2 = new StringBuffer();
			new Thread() {
				public void run() {
					synchronized (s1) {
						s2.append("A");
						synchronized (s2) {
							s2.append("B");
							System.out.print(s1);
							System.out.print(s2);
						}
					}
				}
			}.start();
		}
	}

🔥Synchronized与锁的对比

  1. Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 用Lock锁,JVM将花费较少的时间来调度线程,并具有更好的扩展性(提供更多的子类)

🎁注: 必须确保使用同一个资源的多个线程共用一把锁

五、线程的通信

🔥wait()、notify()、notifyAll()

  1. wait():令当前线程释放对象监控权 ,然后进入等待
  2. notify()唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  3. notifyAll()唤醒正在排队等待资源的所有线程结束等待

传送门>>>康师傅的经典例题:生产者/消费者问题

这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常

🎁总结:多线程个人感觉比较难,不太好理解和运用,多看几遍总结总结
👌 作者算是一名Java初学者,文章如有错误,欢迎评论私信指正,一起学习~~
😊如果文章对小伙伴们来说有用的话,点赞👍关注🔎收藏🍔就是我的最大动力!
🚩不积跬步,无以至千里书接下回,欢迎再见🌹

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 43
    评论
评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的小鳴人

鳴人将永远记住您的恩惠

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值