Java多线程(一篇从0讲透)

本文详细介绍了Java多线程的各个方面,包括线程的五种创建方式、线程的常用操作、线程安全问题及其解决方案,如synchronized和Lock锁。特别强调了线程的并发与并行概念,线程的状态以及线程同步的重要性,同时通过实例解释了生产者消费者问题的解决策略。
摘要由CSDN通过智能技术生成

多线程

思维导图看天下:

1. 概述

并行与并发

并行 :指两个或多个事件在同一时刻发生(同时发生)

并发 :指两个或多个事件在同一个时间段内发生。(交替执行)

线程与进程

进程:是指一个内存中运行的程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程

记忆:进程的英文为Process,Process也为过程,所以进程可以大概理解为程序执行的过程。

(进程也是程序的一次执行过程,是系统运行程序的基本单位; 系统运行一个程序即是一个进程从创建、运行到消亡的过程)

线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。【java默认有两个线程:main、GC】

进程与线程的区别

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多

2. 线程创建的五种方式

推荐使用Runnable接口的方式,因为Java是单继承的,所以使用Thread有OPP单继承局限性

2.1 背景介绍

线程类
Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例
每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码
Java使用线程执行体来代表这段程序流。

2.2 ① 继承Thread类

2.2.1 线程实现

1)实现步骤

  1. 继承Thread类的子类,并重写该类的run()方法(该run()方法的方法体就代表了线程需要完成的任务,因此run()方法称为线程执行体)
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

2)实现案例

自定义线程类:

主函数:

 

public static void main(String[] args) { MyThread myThread = new MyThread("MyThread"); myThread.start(); for (int i = 0;i<1000;i++){ System.out.println("main"+i); } }

执行结果:

3)执行过程分析

过程:程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建

随着调用Mt类的对象的start方法,另外一个新的线程也启动了 ,这样,整个应用就在多线程下运行。

运行时序图:

内存结构:

4)调用start和run方法的区别

2.2.2 构造方法

  1. public Thread()
    分配一个新的线程对象。
  2. public Thread(String name)
    分配一个指定名字的新的线程对象
  3. public Thread(Runnable target)
    分配一个带有指定目标新的线程对象
  4. public Thread(Runnable target,String name)
    分配一个带有指定目标新的线程对象并指定名字

2.2.3 常用方法

  1. public String getName() :获取当前线程名称。
  2. public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
  3. public void run() :此线程要执行的任务在此处定义代码。
  4. public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  5. public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

1)获取线程名称

  1. 可以使用Thread类中的方法getName,
    String getName() 返回该线程的名称。

  2. 可以先获取当前正在执行的线程,再调用getName方法获取线程名称

    static Thread currentThread() 返回对当前正在执行的线程对象的引用

     //1.可以使用Thread类中的方法getName String name = getName(); System.out.println(name);//创建时, 指定了名称,获取的就是指定的名称 //如果没有指定名称,获取的就是Thread-0 //2.可以先获取当前正在执行的线程 Thread currentThread = Thread.currentThread(); System.out.println(currentThread);//Thread[Thread-0,5,main] String name2 = currentThread.getName(); System.out.println(name2);//Thread-0 

2)设置线程名称

  1. 方法一:可以使用Thread类中的方法setName
    void setName(String name) 改变线程名称,使之与参数 name 相同。
 

MyThread myThread = new MyThread(); myThread.setName("myThreadName"); myThread.start();

  1. 方法二:添加一个带参构造方法,参数传递线程的名称;调用父类的带参构造方法,把名字传递给父类,让父亲给儿子起名字
    Thread(String name) 分配新的 Thread 对象。
 

public class MyThread extends Thread{ //定义指定线程名称的构造方法 public MyThread(String name) { super(name); }

3)线程休眠

public static void sleep(long millis)

使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)睡醒了,继续执行

 

/*程序在执行第二秒时, 会暂停2秒,2秒后,继续执行后面程序*/ for (int i = 1; i <=60; i++) { System.out.println(i); /*让程序睡眠1秒钟 1秒=1000毫秒*/ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }

2.2.4 Thread构造方法底层原理——静态代理

只针对有参且参是Runnable类的构造方法:public Thread(Runnable target)

由于Thread和target顶层都是Runnable接口,所以Thread是使用了静态代理的方式代理参数target。

2.3 ② 实现Runnable接口

优势

  1. 避免单继承的局限性
    一个类继承了Thread类就不能继承其他的类
    一个类实现了Runnable接口,还可以继续继承别的类,实现其他的接口
  2. 增强了程序的扩展性,降低程序的耦合度
    使用Runnable接口把设置线程任务和开启线程相分离
    实现类当中,重写run方法,设置线程任务
    创建Thread类对象,调用 start方法,开启新线程

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享

2.3.1 实现步骤

1.创建一个RunnableImpl类实现Runnable接口
2.重写Runnable接口中的run方法,设置线程任务
3.创建Runnable接口的实现类RunnableImpl的对象t
4.创建Thread类对象,构造方法中传递Runnable接口的实现类RunnableImpl的对象t
5.调用Thread类中的start方法,开启新的线程,执行run方法
示例:

 

//实现Runnable接口 public class RunnableImpl implements Runnable{ //2.重写Runnable接口中的run方法,设置线程任务 @Override public void run() { //新线程执行的代码 for (int i = 0; i <20; i++) { System.out.println(Thread.currentThread().getName()+"===>"+i); } } } public static void main(String[] args) { //3.创建Runnable接口的实现类对象 RunnableImpl r = new RunnableImpl(); //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 Thread t = new Thread(r);//打印20次i //5.调用Thread类中的start方法,开启新的线程,执行run方法 t.start(); //【一般16-18行简写为:new Thread(r,"线程名").start();】 //主线程开启新线程之后继续执行的代码 for (int i = 0; i <20; i++) { System.out.println(Thread.currentThread().getName()+"===>"+i); } }

2.3.2 构造方法

  1. Thread(Runnable target) 分配新的 Thread对象
  2. Thread(Runnable target, String name) 分配新的 Thread对象【推荐该方法,因为可以自定义线程名】

2.4 ③ 实现Callable接口

十分重要,但本篇只简单介绍了一下,请去看下一篇JUC

2.4.1 实现步骤

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1); //1为开辟的线程池中线程的数量
  5. 提交执行Future result1 = ser.submit(t1); //线程
  6. 获取结果:boolean r1 = resut1.get() //指定线程的返回结果
  7. 关闭服务:ser.shutdownNow()

代码:

 

public class MyCallableImpl implements Callable<Boolean> { @Override public Boolean call() throws Exception { PictureCatch t = new PictureCatch(); t.test(url,name); System.out.println("下载了文件名:"+name); return true; } String url; //网址 String name; //保存的文件名 MyCallableImpl(String url,String name){ this.url=url; this.name=name; } public static v

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值