Java并发之线程(一)

目标:

  • 线程的状态
  • 线程的几种实现方式
  • 三个线程轮流打印ABC十次
  • 判断线程是否销毁
  • yield功能
  • 给定三个线程t1,t2,t3,如何保证依次执行

1.基本概念

  程序:是一个静态的概念;

  进程:是一个动态的概念

    a.进程是程序的一次动态执行过程,占用特定的地址空间;

    b.每个进程都是独立的,包括三部分:CPU code data

    c.缺点:内存的浪费,增加cpu的负担

  线程:Thread,是进程中的一个‘单一的连续控制流程/执行路径‘;

    a.线程又被称为轻量级进程;

    b.Thread run at the same time,independently of one Another (线程同时运行,彼此独立)

    c.一个进程可以拥有多个并行的线程

    d.一个进程中的线程共享相同的内存单元/可以变换的内存空间地址-->可以访问相同的变量和对象,而且他们从统一堆中分配对象|通信|数据交换|同步操作

    e.由于线程间的通信是在同一内存地址空间中进行的,不需要额外的通信机制,这就使线程间的通信更简便而且信息传递的速度也更快

2.线程的启动

  2.1 实现Runnable接口

    a.自定义一个线程 实现Runnable的run()方法 run()方法就是要执行的内容,会在另一个分支进行.Thread类本身也实现了Runnable接口

    b.主方法中new一个自定义线程对象,然后new一个Thread类对象,其构造方法的参数就是自定义线程对象

    c.执行Thread类的start方法,线程开始执行 自此产生了分支,一个分支会执行run方法,在主方法中不会等待run方法调用完毕返回才继续执行,而是直接继续执行,是第二个分支。这两个分支并行运行

  PS:这里运用了静态代理模式: Thread类和自定义线程类都实现了Runnable接口 Thread类是代理Proxy,自定义线程类是被代理类 通过调用Thread的start方法,实际上调用了自定义线程类的start方法(当然除此之外还有其他的代码)

  2.2 继承Thread类

    a.自定义一个类MyThread,继承Thread类,重写run方法

    b.在main方法中new一个自定义类,然后直接调用start方法 两个方法比较而言第二个方法代码量较少 但是第一个方法比较灵活,自定义线程类还可以继承其他的类,而不限于Thread类

  2.3 实现Callable接口

    

  3. 线程的状态

  初始态:NEW

    创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。

  就绪态 READY

    在Java中,运行态包括就绪态 和 运行态

    该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。 所有就绪态的线程存放在就绪队列中。

  运行态 RUNNING

    获得CPU执行权,正在执行的线程。 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。

  阻塞态 BLOCKED

    阻塞态专指请求排它锁失败时进入的状态。

  等待态 WAITING

    当前线程中调用wait、join、park函数时,当前线程就会进入等待态。 进入等待态的线程会释放CPU执行权,并释放资源(如:锁),它们要等待被其他线程显式地唤醒。

  超时等待态 TIME_WAITING

    当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态; 进入该状态后释放CPU执行权 和 占有的资源。 与等待态的区别:无需等待被其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒。

  终止态

    线程执行结束后的状态。

  

 4. 线程的方法

getName

Thread类的构造方法1  Thread类的构造方法2 

    • new 一个子类对象的同时也new了其父类的对象,只是如果不显式调用父类的构造方法super(),那么会自动调用无参数的父类的构造方法。 可以在自定义类MyThread中(继承自Thread类)中写一个构造方法,显式调用父类的构造方法,其参数为一个字符串,表示创建一个以该字符串为名字的Thread对象。
    • 效果是创建了一个MyThread对象,并且其父类Thread对象的名字是给定的字符串。
    • 如果不显式调用父类的构造方法super(参数),那么默认父类Thread是没有名字的。

isAlive

isAlive活着的定义是就绪、运行、阻塞状态 线程是有优先级的,优先级高的获得Cpu执行时间长,并不代表优先级低的就得不到执行

sleep(当前线程.sleep)

sleep时持有的锁不会自动释放,sleep时可能会抛出InterruptedException。 Thread.sleep(long millis) 一定是当前线程调用此方法,当前线程进入TIME_WAIT状态,但不释放对象锁,millis后线程自动苏醒进入READY状态。作用:给其它线程执行机会的最佳方式。

join(其他线程.join)

t.join()/t.join(long millis) 当前线程里调用线程1的join方法,当前线程进入WAIT状态,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。 join方法的作用是将分出来的线程合并回去,等待分出来的线程执行完毕后继续执行原有线程。类似于方法调用。(相当于调用thead.run())

yield(当前线程.yield)

Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。

interrupt(其他线程.interrupt)

    • 调用Interrupt方法时,线程的中断状态将被置位。这是每一个线程都具有的boolean标志; 中断可以理解为线程的一个标志位属性,表示一个运行中的线程是否被其他线程进行了中断操作。这里提到了其他线程,所以可以认为中断是线程之间进行通信的一种方式,简单来说就是由其他线程通过执行interrupt方法对该线程打个招呼,让起中断标志位为true,从而实现中断线程执行的目的。
    • 其他线程调用了interrupt方法后,该线程通过检查自身是否被中断进行响应,具体就是该线程需要调用Thread.currentThread().isInterrupted方法进行判断是否被中断或者调用Thread类的静态方法interrupted对当前线程的中断标志位进行复位(变为false)。需要注意的是,如果该线程已经处于终结状态,即使该线程被中断过,那么调用isInterrupted方法返回仍然是false,表示没有被中断。
    • 那么是不是线程调用了interrupt方法对该线程进行中断,该线程就会被中断呢?答案是否定的。因为Java虚拟机对会抛出InterruptedException异常的方法进行了特别处理:Java虚拟机会将该线程的中断标志位清除,然后抛出InterruptedException,这个时候调用isInterrupted方法返回的也是false。

interrupt一个其他线程t时

    • 1)如果线程t中调用了可以抛出InterruptedException的方法,那么会在t中抛出InterruptedException并清除中断标志位。
    • 2)如果t没有调用此类方法,那么会正常地将设置中断标志位。

如何停止线程?

    • 1)在catch InterruptedException异常时可以关闭当前线程;
    • 2)循环调用isInterrupted方法检测是否被中断,如果被中断,要么调用interrupted方法清除中断标志位,要么就关闭当前线程。
    • 3)无论1)还是2),都可以通过一个volatile的自定义标志位来控制循环是否继续执行

但是注意! 如果线程中有阻塞操作,在阻塞时是无法去检测中断标志位或自定义标志位的,只能使用1)的interrupt方法才能中断线程,并且在线程停止前关闭引起阻塞的资源(比如Socket)。

wait(对象.wait)

    • 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
    • obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
    • 调用wait()方法的线程,如果其他线程调用该线程的interrupt()方法,则会重新尝试获取对象锁。只有当获取到对象锁,才开始抛出相应的InterruptedException异常,从wait中返回。

notify(对象.notify)

obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

wait&notify 最佳实践

等待方(消费者)和通知方(生产者)

等待方:
synchronized(obj){
	while(条件不满足){
 	obj.wait();
}
消费;
}

通知方:
synchonized(obj){
	改变条件;
	obj.notifyAll();
}
    • 1)条件谓词:
    • 将与条件队列相关联的条件谓词以及在这些条件谓词上等待的操作都写入文档。
    • 在条件等待中存在一种重要的三元关系,包括加锁、wait方法和一个条件谓词。在条件谓词中包含多个状态变量,而状态变量由一个锁来保护,因此在测试条件谓词之前必须先持有这个锁。锁对象和条件队列对象(即调用wait和notify等方法所在的对象)必须是同一个对象。
    • 当线程从wait方法中被唤醒时,它在重新请求锁时不具有任何特殊的优先性,而要去其他尝试进入同步代码块的线程一起正常地在锁上进行竞争。
    • 每一次wait调用都会隐式地与特定的条件谓词关联起来。当调用某个特定条件谓词的wait时,调用者必须已经持有与条件队列相关的锁,并且这个锁必须保护着构成条件谓词的状态变量。
    • 2)过早唤醒: 虽然在锁、条件谓词和条件队列之间的三元关系并不复杂,但wait方法的返回并不一定意味着线程正在等待的条件谓词已经变成真了。 当执行控制重新进入调用wait的代码时,它已经重新获取了与条件队列相关联的锁。现在条件谓词是不是已经变为真了?或许。在发出通知的线程调用notifyAll时,条件谓词可能已经变成真,但在重新获取锁时将再次变成假。在线程被唤醒到wait重新获取锁的这段时间里,可能有其他线程已经获取了这个锁,并修改了对象的状态。或者,条件谓词从调用wait起根本就没有变成真。你并不知道另一个线程为什么调用notify或notifyAll,也许是因为与同一条件队列相关的另一个条件谓词变成了真。一个条件队列与多个条件谓词相关是一种很常见的情况。 基于所有这些原因,每当线程从wait中唤醒时,都必须再次测试条件谓词。
    • 3)notify与notifyAll: 由于多个线程可以基于不同的条件谓词在同一个条件队列上等待,因此如果使用notify而不是notifyAll,那么将是一种危险的操作,因为单一的通知很容易导致类似于信号地址(线程必须等待一个已经为真的条件,但在开始等待之前没有检查条件谓词)的问题。

只有同时满足以下两个条件时,才能用单一的notify而不是notifyAll:

    • 1)所有等待线程的类型都相同。只有一个条件谓词与条件队列相关,并且每个线程在从wait返回后将执行相同的操作。
    • 2)单进单出:在对象状态上的每次改变,最多只能唤醒一个线程来执行。

suspend resume stop destroy(废弃方法)

    • 线程的暂停、恢复、停止对应的就是suspend、resume和stop/destroy。
    • suspend会使当前线程进入阻塞状态并不释放占有的资源,容易引起死锁;
    • stop在结束一个线程时不会去释放占用的资源。它会直接终止run方法的调用,并且会抛出一个ThreadDeath错误。
    • destroy只是抛出一个NoSuchMethodError。
    • suspend和resume已被wait、notify取代。


    • 判断当前线程是否正在执行 注意优先级是概率而非先后顺序(优先级高可能会执行时间长,但也不一定)

      线程优先级特性:

      • 继承性 比如A线程启动B线程,则B线程的优先级与A是一样的。
      • 规则性 高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
      • 随机性 优先级较高的线程不一定每一次都先执行完。 注意,在不同的JVM以及OS上,线程规划会存在差异,有些OS会忽略对线程优先级的设定。

      守护线程

      • 将线程转换为守护线程
      • 守护线程的唯一用途是为其他线程提供服务。比如计时线程,它定时发送信号给其他线程;
      • 当只剩下守护线程时,JVM就退出了。
      • 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
      • 注意!Java虚拟机退出时Daemon线程中的finally块并不一定会被执行。

      未捕获异常处理器

      在Runnable的run方法中不能抛出异常,如果某个异常没有被捕获,则会导致线程终止。

      要求异常处理器实现Thread.UncaughtExceptionHandler接口。 可以使用setUncaughtExceptionHandler方法为任何一个线程安装一个处理器, 也可以使用Thread.setDefaultUncaughtExceptionHandler方法为所有线程安装一个默认的处理器;

      如果不安装默认的处理器,那么默认的处理器为空。如果不为独立的线程安装处理器,此时的处理器就是该线程的ThreadGroup对象 ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,它的uncaughtException方法做如下操作:

      • 1)如果该线程组有父线程组,那么父线程组的uncaughtException方法被调用。
      • 2)否则,如果Thread.getDefaultExceptionHandler方法返回一个非空的处理器,则调用该处理器。
      • 3)否则,如果Throwable是ThreadDeath的一个实例(ThreadDeath对象由stop方法产生,而该方法已过时),什么都不做。
      • 4)否则,线程的名字以及Throwable的栈踪迹被输出到System.err上。

      如果是由线程池ThreadPoolExecutor执行任务,只有通过execute提交的任务,才能将它抛出的异常交给UncaughtExceptionHandler,而通过submit提交的任务,无论是抛出的未检测异常还是已检查异常,都将被认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。

原文地址:https://www.cnblogs.com/assistants/p/12010158.html

时间: 2024-11-09 03:15:48

Java并发之线程(一)的相关文章

Java并发之——线程池

一. 线程池介绍 1.1 简介 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理.当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源. 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项

java并发之线程执行器(Executor)

线程执行器和不使用线程执行器的对比(优缺点) 1.线程执行器分离了任务的创建和执行,通过使用执行器,只需要实现Runnable接口的对象,然后把这些对象发送给执行器即可. 2.使用线程池来提高程序的性能.当发送一个任务给执行器时,执行器会尝试使用线程池中的线程来执行这个任务.避免了不断创建和销毁线程导致的性能开销. 3.执行器可以处理实现了Callable接口的任务.Callable接口类似于Runnable接口,却提供了两方面的增强: a.Callable主方法名称为call(),可以返回结果

java并发之线程的创建(一)

概论 最近在学习并发,于是我在网上搜了一本<java并发编程实战>书学习. 传统创建线程的方式(jdk 1.5之前的方式) 在我印象中创建线程有两种方式 1. 继承Thread类,重写run方法,实例化自己写Thread子类,并用start()方法开启. 2.实现Runnable接口,重写run方法,把Runnable的子类的实例对象作为Thread的构造参数传递进去,创建线程,并开启. 但是我看别人代码时大部分都用第一种方式,直接new Thread 然后重写run方法.其实第二种方式更加符

Java并发之线程间协作Object的wait()、notify()、notifyAll()

wait().notify()和notifyAll()是Object类中的方法: 1)wait().notify()和notifyAll()方法是本地方法,而且为final方法,无法被重写. 2)调用某个对象的wait()方法能让当前线程堵塞.而且当前线程必须拥有此对象的monitor(即锁) 3)调用某个对象的notify()方法可以唤醒一个正在等待这个对象的monitor的线程,假设有多个线程都在等待这个对象的     monitor.则仅仅能唤醒当中一个线程: 4)调用notifyAll(

java并发之线程池Executor 核心源码解析

1.什么是线程池 定义:线程池是指管理一组同构工作线程的资源池 组成部分: 线程管理器(ThreadPool):用于创建并管理线程池.包括创建线程池,销毁线程池,添加新任务 工作线程(PoolWorker):线程池中的线程 任务接口(Task):每个任务必须实现的接口,一共工作线程调度任务的执行 任务队列:用于存放没有处理的任务,提供一种缓冲机制 2.为什么要使用线程池 通过重用现有的线程而不是创建新线程,从而减少了线程创建 和 销毁过程中的巨大开销 当请求到达时,工作线程已经存在,不用再等待线

JAVA并发之阻塞队列浅析

背景 因为在工作中经常会用到阻塞队列,有的时候还要根据业务场景获取重写阻塞队列中的方法,所以学习一下阻塞队列的实现原理还是很有必要的.(PS:不深入了解的话,很容易使用出错,造成没有技术深度的样子) 阻塞队列是什么? 要想了解阻塞队列,先了解一下队列是啥,简单的说队列就是一种先进先出的数据结构.(具体的内容去数据结构里学习一下)所以阻塞队列就是一种可阻塞的队列.和普通的队列的不同就体现在 ”阻塞“两个字上.阻塞是啥意思? 百度看一下 在软件工程里阻塞一般指的是阻塞调用,即调用结果返回之前,当前线

深入剖析java并发之阻塞队列LinkedBlockingQueue与ArrayBlockingQueue

关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入理解Java并发之synchronized实现原理 Java并发编程-无锁CAS与Unsafe类及其并发包Atomic 深入理解Java内存模型(JMM)及volatile关键字 剖析基于并发AQS的重入锁(ReetrantLock)及其Condition实现原理 剖析基于并发AQS的共

Java并发之CountDownLatch的使用

Java并发之CountDownLatch的使用 一. 简介 Java的并发包早在JDK5这个版本中就已经推出,而且Java的并发编程是几乎每个Java程序员都无法绕开的屏障.笔者今晚在家闲来无事,翻看了以前的博客,发现好久都没有写过博客,就想着写点东西,写点什么好了,思来想去很久,决定在这段时间里写写关于Java并发相关的东西.由于是突然兴起,所有就没有什么规划,想到什么就写点什么吧,没想到首先想到的就是CountDownLatch的这个类,那就说说这个类吧. 二. CountDownLatc

Java并发之CyclicBarria的使用

Java并发之CyclicBarria的使用 一.简介 笔者在写CountDownLatch这个类的时候,看到了博客园上的<浅析Java中CountDownLatch用法>这篇博文,为博主扎实的技术功底所折服,对Java多线程方面的只是信手拈来,首先在此感谢博主给了我灵感,让我进一步了解了CountDownLatch的用法,在此请收下小弟的膝盖(如果博主能够看到的化).借着<浅析Java中CountDownLatch用法>这篇博文,笔者想借着这个例子说一下 CyclicBarria