java多线程

本文详细介绍了Java中实现多线程的四种方式,包括继承Thread、实现Runnable接口、实现Callable接口以及使用线程池,并分析了各种方式的优缺点。同时,文章深入探讨了线程安全问题,包括同步机制、线程通信以及在实际问题中的应用。最后,提到了多线程在JVM内存结构中的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

一、为什么使用多线程

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率(我们知道现在的cpu一般都是多核的,多核情况下
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

二、java多线程的创建方式

方式1:继承Thread,重写run()方法
//实现输出100内的偶数
class demo extends Thread{
public void run()
{
	for(int i=0;i<=100;i++)
	{
		if(i%2==0)
		{
			System.out.println(i);
		}
	}
}
}
public class day1{
public void main(String [] args)
{
	demo d1=new demo();
	d1.setName("线程1");
	//设置线程的优先级
	d1.setPriority(Thread.MAX_PRIORITY);
	d1.start()
}
}

在这种方式中怎么实现真正的多线程,比如说有数据共享,利用静态static 修饰符,你创建多个对象我也只有一个。

方式2:实现Runnable接口,重写其中的run()方法,生成对象传递给Thread构造器

共用同一个对象用于实现数据共享,多个平台卖100张票的例子

class Window1 implements Runnable{
	public int ticket=100;
	public void run()
	{
		while(true){
		         if(ticket > 0){
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                ticket--;
            }else{
                break;
            }
        }
	}
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }

}
方式3:实现Callable接口

创建实现Callabel接口的实现类,实现call方法
创建Callable接口实现类的对象
将此对象传递到FutureTask构造器中,创建FutureTask的对象
将FutureTask的对象传递给Thread类的构造器中,创建Thread对象,调用start方法。
获取call方法的返回值。

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
  1. call()可以有返回值的。
  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
  3. Callable是支持泛型的
class NumThread implements
方式4:创建线程池
优势
  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止

在实际情况中,我们需要依赖ExecutorService接口来实现,ExecutorService的实现类是ThreadPoolExecutor,我们可以通过该实现类的对象来设置线程池的相关属性。

//设置
ExecutorService service=Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1=(ThreadPoolExecutor) service;
service1.setCorePoolSize(15);
service1.setKeepAliveTime();
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();

三、多种实现方式的比较

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

  1. 实现的方式没有类的单继承性的局限性,因为要继承Thread类的类(window)自己也有一套继承体系,但是由于java只允许单继承,所以继承一个类就无法继承其他类,java中为什么会出现接口,就是为了破单继承。
  2. 实现的方式更适合来处理多个线程有共享数据的情况。
  3. 联系:public class Thread implements Runnable
  4. 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

四、线程的分类

守护线程、用户线程

五、线程的生命周期

Thread.State

六、线程的安全问题

####问题概述

  1. 实际问题引出:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
  2. 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
  3. 解决方案:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
Java中的同步机制
同步代码块
  1. 代码
  synchronized(同步监视器){
      //需要被同步的代码
  }
  1. 操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了
    包含代码少了依然会有线程安全问题
    包含代码多了,可能想达到的并行效果就没有了
    举一个例子:
    下面的代码中是不能将while(true)包含到同步代码块中的,这样会导致一个窗口把所有的票都卖完(他拿到了锁,不出去了。。。),更不能把run方法直接包进去
        while(true){
            synchronized (this){//此时的this:唯一的Window1的对象   //方式二:synchronized (dog) {

                if (ticket > 0) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);


                    ticket--;
                } else {
                    break;
                }
            }
        }
  1. 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。

  2. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
    要求:多个线程必须要共用同一把锁。

  3. 同步代码块解决继承Thread类线程安全问题

  4. 同步代码块解决实现Runnable接口的线程安全问题

同步方法
  1. 代码
public sychronized void show()
{
	//其中放处理共享数据的代码
}
  1. 同步方法解决继承Thread类线程安全问题
    注意该方法要声明称静态的,为了保证监视器的唯一,静态方法的代码中的监视器是类
  2. 同步方法解决实现Runnable接口的线程安全问题
  3. 关于同步方法的总结:
    同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
    非静态的同步方法,同步监视器是:this
    静态的同步方法,同步监视器是:当前类本身
lock锁
不同线程的实现方式导致的同步机制的实现的注意事项

同步监视器(锁)的唯一性的要求

  1. 继承Thread类的方式
    慎用this充当监视器,但是可以用xxx.class代替(体现万物皆对象,类也是对象,并且解释一个问题,案例可以体现类也是对象的代码的回答)

  2. 实现Runnable的方式

同步方式存在的局限性
  1. 优点:同步的方式,解决了线程的安全问题。
  2. 局限性:操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。

七、多线程之间的通信

涉及到的三个方法:
  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
    方法使用说明:
  • wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
  • wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,
  • 不能出现监视器与这三个方法调用者不一致的情况否则,会出现IllegalMonitorStateException异常
  • 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
sleep()方法与wait的比较

sleep方法是要抛异常的,我们需要捕获异常进行处理。

  • 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
  • 2.不同点:
    • 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
    • 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
    • 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

八、 面对实际问题时怎么使用多线程

  1. 判断是否时多线程问题
    判断是否存在多个线程
  2. 判断是否存在线程安全问题
    判断是否存在共享数据
  3. 共享数据的类怎么生命保证只有一份?
    在各个线程中都声明一个共享数据类的对象,并且将其作作为线程子类的一个属性
    4.处理线程安全问题

九、多线程在JVM中内存的结构

JVM内存结构
其中方法区与堆是多个线程共用一个,也就是一个进程一份,而虚拟机栈与程序计数器是一个线程一个,多个线 程具有多个。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值