java 多线程 您所在的位置:网站首页 龟兔赛跑java多线程 java 多线程

java 多线程

2024-07-14 04:18| 来源: 网络整理| 查看: 265

目录

第一节、线程的定义和创建

1.1 进程和线程

1.2 线程的定义和创建1:继承Thread类

1.3 线程定义和创建2:实现Runnable接口

1.4 线程定义和创建3:实现Callable接口 

第二节 线程控制

2.1 线程的生命周期

2.2 线程控制

第三节:线程同步

3.1 线程安全问题

3.2 同步代码块

3.3 同步方法

3.4 死锁

第四节 线程同步

4.1 Lock锁

4.2 ReadWriteLock锁

4.3 Lock锁和同步锁(synchronized)的选择

4.4 volatile 关键字

4.5 CAS和ABA问题

第一节、线程的定义和创建 1.1 进程和线程

程序Program

        程序是一段静态的代码,它是应用程序执行的蓝本

进程Process

        进程是一种正在运行的程序,有自己的地址空间

 进程的特点

        动态性

        并发性

        独立性

        并发和并行的区别

                并行:多个CPU同时执行多个任务

                并发:一个CPU(采用时间片)同时执行多个任务

生活案例:并发和并行的区别

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行

        

线程Thread

        进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。        线程又被称为轻量级进程(lightweight process)        如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程

线程特点

        轻量级进程        独立调度的基本单位        共享进程资源        可并发执行

 线程和进程的区别

区别

进程

线程

根本区别

作为资源分配的单位

调度和执行的单位

开    销

每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。

线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。

所处环境

在操作系统中能同时运行多个任务(程序)

在同一应用程序中有多个顺序流同时执行

分配内存

系统在运行的时候会为每个进程分配不同的内存区域

除了CPU外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源

包含关系

没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。

线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。

1.2 线程的定义和创建1:继承Thread类

Thread类是Java提供的线程顶级类,继承Thread类可快速定义线程。

【示例1】 使用多线程实现龟兔赛跑

package com.bjsxt.threadDemo1; public class Test1 { public static void main(String[] args) { /* * 目标:通过多线程形式实现龟兔赛跑 * 步骤: 1准备乌龟线程 * 2准备兔子线程 * 3启动两个线程 调用start方法 不要直接调用run方法 * */ /*Thread.currentThread方法获得当前线程对象 * 哪个线程在执行这一行代码 返回的对象就是这个线程 * * */ Thread thread = Thread.currentThread(); System.out.println(thread.getName()); System.out.println(thread.getPriority()); RunnerThread wugui =new RunnerThread(); RunnerThread tuzi =new RunnerThread(); // 设置线程的优先级 wugui.setPriority(1); tuzi.setPriority(10); //获得线程的优先级 System.out.println(wugui.getPriority()); System.out.println(tuzi.getPriority()); //设置线程名字 wugui.setName("乌龟"); tuzi.setName("兔子"); // 启动线程 wugui.start(); tuzi.start(); } } /* * 1继承Thread类 * 2重写run方法 定义线程任务的方法 将线程要执行的工作放在run方法中即可 * * */ class RunnerThread extends Thread{ public RunnerThread(){} public RunnerThread(String name){ super(name); } @Override public void run() { Thread thread = Thread.currentThread(); System.out.println(thread.getName()); System.out.println(thread.getPriority()); for (int i = 1; i { Thread.sleep(5000); return new Random().nextInt(10); };

第三种方式:实现Callable接口

与实现Runnable相比,Callable功能更强大些

        方法名不同        可以有返回值,支持泛型的返回值        可以抛出检查异常        需要借助Future,比如获取返回结果

Future接口

        可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等

        FutureTask是Future接口的唯一的实现类

        FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

第二节 线程控制 2.1 线程的生命周期

新生状态

        用new关键字建立一个线程对象后,该线程对象就处于新生状态。        处于新生状态的线程有自己的内存空间,通过调用start进入就绪状态

就绪状态

        处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU        当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称之为“CPU调度”

运行状态

        在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任务而死亡。        如果在给定的时间内没执行结束,就会被系统给换下来回到等待执行状态。

阻塞状态

        处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。        在阻塞状态下的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续进行。

死亡状态

        死亡状态是线程生命周期中最后一个阶段。线程死亡原因有三个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性的终止,如通过执行stop方法来终止一个线程【不推荐使用】,三是线程抛出未捕获的异常

总结:线程生命周期

运行状态的出口:阻塞、就绪、终止就绪状态的入口:新生、运行、阻塞讲解了线程同步、线程通信后会增加更多的状态:增加了两种阻塞状态CPU的执行权 占用CPU运行的权利  运行状态CPU的等待权 等待CPU执行的权利  就绪状态 2.2 线程控制

        理解了线程生命周期的基础上,可以使用java提供的线程控制命令对线程的生命周期进行干预

join()

阻塞指定线程等到另一个线程完成以后再继续执行。

sleep()

是线程停止一段时间让出CPU,将处于阻塞状态

如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行

实际开发中经常使用Thread.sleep()来模拟线程切换,暴露线程安全问题

yield()

让当前正在执行的线程暂停,不是阻塞线程,而是将现在的线程转入就绪状态

如果调用了yield方法后,没有其他等待执行的线程,这个时候当前线程就会马上恢复执行

setDaemon()

可以将指定的线程设置成为后台线程

创建后台线程的线程结束时,后台线程也随之消亡

只能在线程启动之前把它设为后台线程

interrupt()

并没有直接中断线程,而是需要被中断线程自己处理

stop()

结束线程,不推荐使用 

【示例4】使用join()阻塞当前线程

package com.bjsxt.threadDemo3; public class Test1 { public static void main(String[] args) throws InterruptedException { RunnerThread runnerThread = new RunnerThread(); runnerThread.setName("兔子"); RunnerThread runnerThread2 = new RunnerThread(); runnerThread2.setName("乌龟"); runnerThread.start(); runnerThread2.start(); // 让其他线程加入当前线程 // 让乌龟和兔子线程加入主方法线程 runnerThread.join(); runnerThread2.join(); System.out.println("比赛结束"); } } class RunnerThread extends Thread{ public RunnerThread(){ } public RunnerThread(String name){ // 调用父类构造方法,设置名字 super(name); } @Override public void run() { for (int i = 1; i       Lock readLock();         Lock writeLock(); }

一个用来获取读锁,一个用来获取写锁。也就是将文件的读写操作分开,分成 2个锁来分配给线程,从而使得多个线程可以同时进行读操作。

ReadWriteLock是一个接口,ReentrantReadWriteLock是它的实现类,该类中包括两个内部类ReadLock和WriteLock,这两个内部类实现了Lock接口

ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:

ReadLock()和WriteLock()用来获取读锁和写锁

【示例13】认识ReadWriteLock锁

public class TestLock { public static void main(String[] args) { //默认也是非公平锁 也是可重入锁 ReadWriteLock rwl = new ReentrantReadWriteLock(); //多次返回的都是同一把读锁 同一把写锁 Lock readLock = rwl.readLock(); Lock readLock2 = rwl.readLock(); Lock writeLock = rwl.writeLock(); readLock.lock(); readLock.unlock(); System.out.println(readLock==readLock2); } }

注意:从结果中看到,从一个ReadWriteLock中多次获取的ReadLock、WriteLock是同一把读锁,同一把写锁

【示例14】ReadWriteLock示例

package com.bjsxt.threadDemo8; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class Test1 { public static void main(String[] args) { for (int i = 1; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有