Java多线程详解

Uma ·
更新时间:2024-09-20
· 846 次阅读

文章目录1、进程与线程2、创建多线程2.1、继承Thread类2.2、实现Runnable接口2.3、使用匿名内部类实现2.4、实现Runnable接口的好处2.5、使用Callable和Future创建线程3、线程的生命周期4、几种特殊线程4.1、join线程4.2、守护线程4.3、休眠线程5、线程安全与线程同步5.1、安全问题5.2、解决方案一 ---- 同步代码块5.3、解决方案二 ---- 同步方法6、线程通信6.1、API6.2、案例7、线程池7.1、线程池介绍7.2、线程池的使用 1、进程与线程

   进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。线程(Thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
   程序启动运行main方法的时候,Java虚拟机会启动一个进程,并创建主线程main。随着调用Thread对象的 start(),另外一个新的线程也启动了,整个应用就在多线程下运行。 所以,一个进程可以包括多个线程。而多进程是对于操作系统和CPU而言的,比如电脑同时启动了QQ、微信、音乐软件等应用程序时,每个程序都是一个进程,此时CPU就是多进程的。

2、创建多线程 2.1、继承Thread类 /** * @author RuiMing Lin * @date 2020-03-12 14:19 */ public class MyThread extends Thread { @Override public void run() { System.out.println("mythread1"); } public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); } } 2.2、实现Runnable接口 /** * @author RuiMing Lin * @date 2020-03-12 14:22 */ public class MyRunnable implements Runnable{ @Override public void run() { System.out.println("MyRunnable 1"); } public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); } } 2.3、使用匿名内部类实现 /** * @author RuiMing Lin * @date 2020-03-12 14:25 */ public class Demo1 { public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("使用匿名内部类实现"); } }); thread1.start(); } } 2.4、实现Runnable接口的好处

  实现Runnable接口比继承Thread类所具有的优势:

可以避免java中的单继承的局限性; 增加程序的健壮性,实现解耦操作; 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。 2.5、使用Callable和Future创建线程

  Callable是一个接口,有点类似于Runnable接口,不过较之更加强大。Callable提供了一个类似于run方法的call(),但call()更加强大。主要体现在两点:call()可以有返回值,call()可以抛出异常。
  理论上我们可以类似于上述匿名内部类的方法将Callable接口传递给Thread的构造方法,但实际上不行,因为Thread类提供的构造方法没有传递Callable参数类型的,而Callable又不是Runnable的子接口。所以就有了Future接口,该接口是Runnable的子接口,并有一个实现类FutureTask封装了Callable接口。
在这里插入图片描述

import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; /** * @author RuiMing Lin * @date 2020-03-12 14:25 */ public class Demo1 { public static void main(String[] args) throws Exception{ FutureTask futureTask = new FutureTask(new Callable() { @Override public Integer call() throws Exception { int i = 0; for ( ;i < 10; i++){ System.out.println(Thread.currentThread().getName() + "i = " + i); } return i; } }); new Thread(futureTask).start(); Integer integer = futureTask.get();//返回call()的返回值,必须等到子栈结束后才会得到返回值,会导致程序阻塞 System.out.println("integer = " + integer); } } 3、线程的生命周期

新建态: 当一个线程被new出来,也就是被创建出来的时候;
就绪态: 被创建的线程调用start()方法
     阻塞态线程休眠时间到或者获得同步锁等等;
运行态: 就绪态线程获得CPU资源;
阻塞态: 运行态线程调用sleep()方法进行休眠或者等待同步锁中;
死亡态: 线程被关闭或者出现异常等等。
在这里插入图片描述

4、几种特殊线程 4.1、join线程

Thread的join()可以让一个线程等待调用该方法的线程完全完成的方法:

/** * @author RuiMing Lin * @date 2020-03-13 20:39 */ public class MyRunnable1 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + "执行了" + i + "次"); } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { if (i == 20){ Thread thread = new Thread(new MyRunnable1()); thread.setName("我的线程"); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("主线程main方法执行了" + i + "次"); } } }

结果输出为:
在这里插入图片描述
如果不使用join()方法:

/** * @author RuiMing Lin * @date 2020-03-13 20:39 */ public class MyRunnable1 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + "执行了" + i + "次"); } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { if (i == 20){ Thread thread = new Thread(new MyRunnable1()); thread.setName("我的线程"); thread.start(); // try { // thread.join(); // } catch (InterruptedException e) { // e.printStackTrace(); // } } System.out.println("主线程main方法执行了" + i + "次"); } } }

结果输出为:
在这里插入图片描述
  原因分析:当主线程的i = 20时,创建自定义线程,开启自定义线程并调用join方法,此时主线程main()被挂起,直到自定义线程完全执行结束后才放行让主线程执行。

4.2、守护线程

守护线程表示只要被守护的线程一结束,它也就被结束了。调用方法:

thread.setDaemon(true);

4.3、休眠线程

运行态的线程调用sleep()进入休眠线程,调用方法:

Thread.sleep(1000);

5、线程安全与线程同步 5.1、安全问题

先抛出一个线程之间的安全问题:如今有十张火车票,两个窗口同时售卖

/** * @author RuiMing Lin * @date 2020-03-13 15:57 */ public class Ticket1 implements Runnable{ private int ticket = 10; // 表示有十张火车票 @Override public void run() { for (int i = 0; i 0){ System.out.println(Thread.currentThread().getName() + "卖了编号为" + ticket + "的火车票"); ticket --; try { Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); } } } } public static void main(String[] args) { Ticket1 ticket = new Ticket1(); Thread thread1 = new Thread(ticket); Thread thread2 = new Thread(ticket); thread1.setName("窗口A"); thread2.setName("窗口B"); thread1.start(); thread2.start(); } }

结果输出为:

窗口A卖了编号为10的火车票
窗口B卖了编号为9的火车票
窗口A卖了编号为8的火车票
窗口B卖了编号为8的火车票
窗口A卖了编号为6的火车票
窗口B卖了编号为6的火车票
窗口B卖了编号为4的火车票
窗口A卖了编号为4的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为2的火车票

  很明显,这样的结果是不满足需求的。原因分析:因为多线程是CPU随机执行的,当线程一执行System.out.println(Thread.currentThread().getName()+ “卖了编号为” + ticket + “的火车票”)语句时,还未执行ticket --跳转到线程二,此时就会出现数据没有及时更新、同时售卖同一张票的情况。

5.2、解决方案一 ---- 同步代码块

  对需要连续执行的代码加一把锁,称为同步代码块。代码代码块中的代码必须执行完才会切换到其他线程,这样就能保证了线程之间的安全。

/** * @author RuiMing Lin * @date 2020-03-13 15:57 */ public class Ticket2 implements Runnable{ private int ticket = 10; // 表示有十张火车票 @Override public void run() { for (int i = 0; i 0){ System.out.println(Thread.currentThread().getName() + "卖了编号为" + ticket + "的火车票"); ticket --; try { Thread.sleep(400); }catch (InterruptedException e){ e.printStackTrace(); } } } } } public static void main(String[] args) { Ticket2 ticket = new Ticket2(); Thread thread1 = new Thread(ticket); Thread thread2 = new Thread(ticket); thread1.setName("窗口A"); thread2.setName("窗口B"); thread1.start(); thread2.start(); } }

结果输出为:

窗口A卖了编号为10的火车票
窗口B卖了编号为9的火车票
窗口B卖了编号为8的火车票
窗口B卖了编号为7的火车票
窗口B卖了编号为6的火车票
窗口B卖了编号为5的火车票
窗口B卖了编号为4的火车票
窗口B卖了编号为3的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为1的火车票

可以看出,这样就满足需求了。

5.3、解决方案二 ---- 同步方法 /** * @author RuiMing Lin * @date 2020-03-13 15:57 */ public class Ticket2 implements Runnable{ private int ticket = 10; // 表示有十张火车票 private synchronized void sale(){ if (ticket > 0){ System.out.println(Thread.currentThread().getName() + "卖了编号为" + ticket + "的火车票"); ticket --; try { Thread.sleep(400); }catch (InterruptedException e){ e.printStackTrace(); } } } @Override public void run() { for (int i = 0; i < 10; i++) { sale(); } } public static void main(String[] args) { Ticket2 ticket = new Ticket2(); Thread thread1 = new Thread(ticket); Thread thread2 = new Thread(ticket); thread1.setName("窗口A"); thread2.setName("窗口B"); thread1.start(); thread2.start(); } }

结果输出:

窗口A卖了编号为10的火车票
窗口A卖了编号为9的火车票
窗口A卖了编号为8的火车票
窗口A卖了编号为7的火车票
窗口A卖了编号为6的火车票
窗口A卖了编号为5的火车票
窗口B卖了编号为4的火车票
窗口A卖了编号为3的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为1的火车票

6、线程通信 6.1、API

wait():导致当前线程等待;
notify():唤醒共享当前同一把锁的线程其中之一;
notifyAll():唤醒共享当前同一把锁的所有线程。

6.2、案例

需求:实现1与2交替出现

/** * @author RuiMing Lin * @date 2020-03-13 21:03 */ public class Communication { public static Object lock = new Object(); // 定义一把锁 public static void main(String[] args) { MyThread1 myThread1 = new MyThread1(); MyThread2 myThread2 = new MyThread2(); myThread1.start(); myThread2.start(); } } // 定义通讯类一 class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (Communication.lock){ //互相通信的线程必须共享同一把锁 System.out.print(1); Communication.lock.notify(); try { Communication.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class MyThread2 extends Thread{ @Override public void run() { for (int j = 0; j < 10; j++) { synchronized (Communication.lock){ System.out.print(2); Communication.lock.notify(); try { Communication.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } 7、线程池 7.1、线程池介绍

  线程池是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。这样做有三个好处:1.降低资源消耗,减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;2. 提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行;3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下。

7.2、线程池的使用 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author RuiMing Lin * @date 2020-03-13 21:22 */ public class Pool { public static void main(String[] args) { // 1.创建一个容量为5的线程池 ExecutorService service = Executors.newFixedThreadPool(5); // 2.创建Runnable对象 MyRunnable runnable1 = new MyRunnable(); MyRunnable runnable2 = new MyRunnable(); MyRunnable runnable3 = new MyRunnable(); MyRunnable runnable4 = new MyRunnable(); MyRunnable runnable5 = new MyRunnable(); // 3.调用线程池方法 service.submit(runnable1); service.submit(runnable2); service.submit(runnable3); service.submit(runnable4); service.submit(runnable5); // 4.无需关闭线程,run()结束后自动将线程归还给线程池 // 5.关闭线程池 service.shutdown(); } }

有错误的地方敬请指出!觉得写得可以的话麻烦给个赞!欢迎大家评论区或者私信交流!


作者:1/4糖柠檬茶



JAVA 线程 java多线程

需要 登录 后方可回复, 如果你还没有账号请 注册新账号