java多线程|万字梳理java多线程的基础知识

关于java的线程的这些基本知识你真的掌握了吗
文章目录

  • 关于java的线程的这些基本知识你真的掌握了吗
    • 前言
    • 一、线程是什么?
    • 二、多线程的引入
        • 1.为什么引入多线程
        • 2. 何时需要多线程
    • 多线程的四种创建方式
        • 方式一:继承Thread类
        • 方式二:实现Runnable接口
        • 前面两种创建多线程方式的对比
        • Thread类中的常用方法
        • 方式三:**实现Callable接口**
        • 方式四:**使用线程池**
      • 线程的同步
      • 线程通信
  • 总结

前言 作者本身是一个以Java为主语言的学习者,本篇文章将会为你分享我在学习java这门语言过程中,我们应该必须掌握的一些关于线程方面的基本知识,这篇文章作为你查缺补漏的一个参考利器,希望可以对你有所帮助。
一、线程是什么? 首先我们要知道什么是程序,什么是进程?
  • 程序:程序是为完成特定任务、用某种语言编写的一组指令的集合。是一段静态的代码,即静态对象。记住这么个公式:程序=数据结构+算法+文档。
  • 进程:进程是程序的一次执行过程,或是一个正在运行的程序,是一个动态的过程,即有自身的生命周期。进程是竞争计算机资源分配的基本单位。
知道了上面这两个基本的概念以后我们来讲一讲什么是线程:
  • 线程:进程可以进一步细化为线程,是一个程序内部的一条执行路径。线程作为调度和执行的基本单位,每个线程都拥有自己独立的运行栈和程序计数器。
本文对程序、进程、线程的基本概念介绍到此。如果你对进程,线程等相关的基础知识还有一个更为细致的了解,可以去参考教材《操作系统》。
二、多线程的引入 1.为什么引入多线程 首先我们要知道多线程的优点:
①提高应用程序的响应。对图形化界面更有意义,可增强用户的体验。
②提高计算机系统的CPU的利用率。
③改善程序结构。
2. 何时需要多线程
  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。
补充一点:线程的生命周期
调用start方法 获取CPU执行权/运行 执行完run/调用线程的stop/出现Error/Exception并且没有处理 失去CPU执行权 sleep/join/等待同步锁/wait/suspend sleep时间到/join结束/获取同步锁/notify/nptifyAll/resume 新建 就绪 运行 死亡 阻塞 多线程的四种创建方式 方式一:继承Thread类 1,创建一个Thread类的子类
2,重写thread类中的方法run() 将此线程要执行的操作写在run方法中
3,创建thread类的子类的对象
4,通过此对象调用start()方法启动线程 。
start()方法的作用:
①启动当前线程 ②调用当前线程的run()方法
第一种方式示例代码
下面这段代码相信大家都看得懂,无非就是创建了一个打印1-100内的偶数的线程。
class ThreadTest extends Thread{ public static final int MAX=100; @Override public void run(){ for(int i=0; i

那么线程创建好了我们一个怎么样去启动线程呢?前面我们也提到过 怎么样去启动,即调用start()方法。首先实例化继承Thread类的类,让它的对象去调用start()方法启动线程。
大家结合我在代码中的注释去理解代码。
话不多说,上代码:
public class MyThread{ public static void main(String[] args) { int max=100; ThreadTest threadTest=new ThreadTest(); threadTest.start(); //不能直接调用run()方法去启动线程//在启动一个线程,遍历100以内的偶数, //不可以让已经执行过start()的线程重新去执行 //此时需要重新new一个对象去调用start()方法 //如下: ThreadTest threadTest1=new ThreadTest(); threadTest1.start(); //如下操作在mian线程中执行 System.out.println(Thread.currentThread().getName()+" " +"hello!"); for(int i=0; i

方式二:实现Runnable接口 ①:创建一个实现Runnable接口的类
②:实现类去重写Runnable中的抽象方法:run()
③:创建实现类的对象
④:将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
⑤:通过Thread类的对象去调用start()方法
代码示例
大家不必过度给关注代码的内容,只需理解创建多线程的方式即可。
class Wicket implements Runnable{ private int ticket=100; @Override public void run(){ while (true){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票数为:" + ticket); ticket--; } else { break; } } }

前面两种创建多线程方式的对比 开发中优先选择实现Runnable接口的方式
原因: ①实现的方式没有单继承的局限性
②实现的方式更适合用来处理多个线程的
相同点:两种方式都需要重写run()方法,并将线程的操作放在run()方法中。
Thread类中的常用方法 紧接着我们先看Thread类中的一些常用方法,方便大家对多线程的一些代码有一个更好的理解。
1,start() :启动当前线程
2, run(): 通常需要重写此方法,将创建的线程要
执行的操作放在其中
3,currentThread() :静态方法,返回当前代码的线程
4, getName() :获取当前线程的名字
5, setName() :设置当前线程的名字
6,yield() :释放当前CPU的执行权
7,join() :在线程 A中调用线程B的join(),此时线程A进入阻塞状态
直到线程B执行完以后,线程A才结束阻塞状态
8,stop() :当执行此方法时,强制结束线程的生命周期
9,sleep() :让当前线程“睡眠”指定的毫秒数
当前线程是阻塞状态
10,isALive() :判断线程是否还存活
关于这些方法的测试大家自己可以去实验一下,体会编程的快乐,哈哈哈!
方式三:实现Callable接口 1,创建一个实现Callable接口的实现类
2,实现call()方法,将此线程要执行的操作声明在call()方法中
3,创建Callable接口的实现类的对象
4,将此Callable接口的实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5,将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。
上代码:还是老打印偶数
6,获取call()方法中的返回值
如何理解实现Callable接口的方式创建多线程比实现Runnable接口的方式创建多线程更强大
①call()方法有返回值
②call()可以抛出异常,被外面捕获,获取异常的信息
③Callable是支持泛型的
class CallableThreadTest implements Callable { private int sum = 0; private static final int MAX = 100; private int i = 0; @Override public Object call() throws Exception { for (; i <=MAX; i++) { if (i % 2 == 0) { System.out.println(i); sum += i; } } return sum; } }

方式四:使用线程池 1,提供指定线程数量的线程池
2,执行指定的线程操作,需要提供实现Runnable接口或者实现Callable接口的实现类的对象
3,关闭连接池
corePoolSize:线程池大小
maxmumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间会终止
几点说明:
newFixedThreadPool():静态方法,指定创建的线程池的大小
execute():参数放的是实现类的对象,比如实现Thread类的类,或者是实现Runnable接口的类。
executor.shutdown():关闭线程池。
代码示例:
public class ThreadPollDemo { public static void main(String[] args) { ExecutorService executor= Executors.newFixedThreadPool(10); executor.execute(new ThreadPoolTest()); //适用Runnable executor.execute(new ThreadPoolTestNumber()); executor.shutdown(); // executor.submit()适用于Callable} } class ThreadPoolTest implements Runnable{ private static final int MAX=100; private int i=0; @Override public void run() { for(; i<=MAX; i++){ if(i%2==0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } }class ThreadPoolTestNumber implements Runnable{ private static final int MAX=100; private int i=0; @Override public void run() { for(; i<=MAX; i++){ if(i%2!=0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } }

提到这儿,不得不说使用线程池创建多线程的几个好处:
①提高响应速度
②降低资源消耗
③便于线程管理
下面我们来了解一下关于线程同步安全方面的一些问题
线程的同步
通过同步机制来解决线程的安全问题
方式一:同步代码块
synchronized (同步监视器){
//需要被同步的代码(操作共享数据的代码)
}
什么叫做共享数据?什么叫做同步监视器?
请看解释
弄清楚这两个名词,对于我们处理线程安全问题有有益处。
①共享数据:多个线程共同操作的变量
②同步监视器:俗称 锁。
任何一个类的对象都可以充当锁(要求:多个线程必须共用一个锁)
下面我直接给打家上了一段代码,你可以复制到自己的电脑上跑一跑
package com.threadtest; /** * @author wch * @version jdk 1.8 * @date 2020/12/27 22:42 * @Description 使用实现Runnable接口的方式实现三个窗口卖票 * 存在线程安全问题,出现了重票和错票 * 使用synchronized解决,同步代码块 * */ public class WicketTest { public static void main(String[] args) { Wicket wicket=new Wicket(); Thread thread=new Thread(wicket); Thread thread1=new Thread(wicket); Thread thread2=new Thread(wicket); thread.setName("窗口一"); thread1.setName("窗口二"); thread2.setName("窗口三"); thread.start(); thread1.start(); thread2.start(); } } class Wicket implements Runnable{ private int ticket=100; privatefinalObject obj=new Object(); @Override public void run(){ while (true){ /* 可以用this */ synchronized(this){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票数为:" + ticket); ticket--; } else { break; } } } } }

在代码中相信你很容易看出哪儿时被同步的代码块,但是有一个问题你必须清楚,那就是,在使用关键字synchronized包代码块时不能暴多也不能包少,当然,具体问题具体分析。
方式二:同步方法
如果操作共享数据的代码完整的声明在一个类中,我们不妨将此方法声明为同步的
同步方法仍涉及到同步监视器,只是不需要我们显式的声明
非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身
package com.threadtest; /** * @author wch * @version jdk 1.8 * @date 2020/12/29 19:03 * @Description使用同步方法处理继承Thread类的售票线程安全问题 */ public class ThreadTestOfTickets { public static void main(String[] args) { WindowOfTickets windown=new WindowOfTickets(); WindowOfTickets windown1=new WindowOfTickets(); WindowOfTickets windown2= new WindowOfTickets(); windown.setName("窗口一"); windown1.setName("窗口二"); windown2.setName("窗口三"); windown.start(); windown1.start(); windown2.start(); } } class WindowOfTickets extends Thread{ private static int ticket=100; /** * 同步监视器是当前类本身WindowOfTickets.class */ private static synchronized void show(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":卖票,票数为:" + ticket); ticket--; } } @Override public void run(){ while (ticket>1){ show(); } } }

方式三:Lock锁
package com.locktest; import java.util.concurrent.locks.ReentrantLock; /** * @author wch * @version jdk 1.8 * @date 2020/12/29 20:21 * @Description 用Lock解决线程安全问题 */ public class LockDemo { public static void main(String[] args) { LockTest lockTest=new LockTest(); Thread thread=new Thread(lockTest); Thread thread1=new Thread(lockTest); Thread thread2=new Thread(lockTest); thread.setName("窗口一"); thread1.setName("窗口二"); thread2.setName("窗口三"); thread.start(); thread1.start(); thread2.start(); } } class LockTest implements Runnable{ private int ticket=100; private ReentrantLock reentrantLock=new ReentrantLock(); @Override public void run(){ while (true){ reentrantLock.lock(); try{ if(ticket>1) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票数为:" + ticket); ticket--; } else { break; } } finally { reentrantLock.unlock(); }} } }

使用的优先顺序:Lock->同步代码块->同步方法
synchronized与Lock方法的异同
相同点:都是用来解决线程的同步安全问题
不同点:synchronized机制在执行完相应的同步代码块以后,自动的释放同步监视器。Lock需要手动的启动(lock()),同时结束同步也需要手动的实现(unLock())
同步的方式,解决线程的安全的问题,操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
说明:
1,在实现Runnable接口创建的线程方式中,我们可以考虑用this来充当同步监视器
2,在继承Thread类创建的单线程中,慎用this来充当同步监视器,考虑当前类来充当同步监视器
线程通信
线程通信中涉及到的方法
wait()方法:一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
notify()方法:一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个,则唤醒优先级高的线程
notifyAll()方法:一旦执行此方法,就会唤醒所有被wait()的线程
注意点:
①wait(),notify(),notifyAll(),三个方法必须使用在同步代码块或同步方法中。
②wait(),notify(),notifyAll(),三个方法的调用者,必须是同步代码或同不=步方法的同步监视器。
③wait(),notify(),notifyAll(),三个方法是定义在java.lang.Object类中的。
sleep() 和wait()的异同
相同点:一旦执行这两个方法都可以让线程进入阻塞状态
不同点:
①两个方法声明的位置不一样,Thread类中声明sleep(),Object类中声明wait()。
②调用的要求不同,sleep()可以在任何场景下调用,wait()必须使用在同步代码块或同步方法中
【java多线程|万字梳理java多线程的基础知识】③关于是否释放同步监视器,如果两个方法都是使用在同步代码块或者是同步方法中,sleep()不会释放,wait()会释放同步监视器 。
package com.numbertest; /** * @author wch * @version jdk 1.8 * @date 2020/12/29 21:46 * @Description两个线程交替打印1-100之间的数字 */ public class NumberTest { public static void main(String[] args) { CreateNumber createNumber=new CreateNumber(); Thread thread=new Thread(createNumber); Thread thread1=new Thread(createNumber); thread.setName("线程一:"); thread1.setName("线程二:"); thread.start(); thread1.start(); } } class CreateNumber implements Runnable{ private int number=1; @Override publicvoid run(){ while (true){ synchronized (this) { notify(); if (number <= 100) { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } }

总结 以上就是今天要讲的内容,

    推荐阅读