最新范文 方案 计划 总结 报告 体会 事迹 讲话 倡议书 反思 制度 入党

Java多线程、并发编程知识点总结

日期:2020-11-11  类别:最新范文  编辑:一流范文网  【下载本文Word版

Java多线程、并发编程知识点总结 本文关键词:知识点,并发,多线程,编程,Java

Java多线程、并发编程知识点总结 本文简介:1、线程的状态1.1创建线程的两种方式,接口和线程类。利用接口的好处:更好的体现面向对象的思想,可以避免由于Java的单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;(同步问题)适合多个相同程序代码的线程区处理同一资源的情况。(关注微信订阅号:javaedu)1

Java多线程、并发编程知识点总结 本文内容:

1、线程的状态

1.1创建

线程

的两种方式,接口和线程类。利用接口的好处:更好的体现面向对象的思想,可以避免由于Java的单继承特性而带来的局限;

增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;(同步问题)适合多个相同程序代码的线程区处理同一资源的情况。(关注微信订阅号:javaedu)

1.2线程就绪等待调度运行start()方法。

1.3线程的中断

这里需要注意的是,如果只是单纯的调用interrupt()方法,线程并没有实际被中断,会继续往下执行。

1.4、线程挂起和恢复(挂起还拥有对象锁,死锁)

线程的挂起和恢复实现的正确方法是:通过设置标志位,让线程在安全的位置挂起

1.5

利用多线程模拟同步运行用jion方法,mThread.jion()表示该线程运行完毕后,在运行调用它的线程。

1.6

sleep

休眠

当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断;

1.7stop线程停止

stop方法突然终止线程(持有这些锁必定有某种合适的理由——也许是阻止其他线程访问尚未处于一致性状态的数据,

突然释放锁可能使某些对象中的数据处于不一致状态)

1.8线程可以阻塞于四种状态:(参考资料:t.cn/RA5iKhq)

当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断;

当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒时间为止(若制定了超时值的话)

线程阻塞与不同I/O的方式有多种。常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间

线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得synchronized语句必须的锁时阻塞)。

2、线程的种类

守护线程与线程阻塞的四种情况

Java中有两类线程:User

Thread(用户线程)、Daemon

Thread(守护线程)

用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。

守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。

因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。、

setDaemon(true)必须在调用线程的start()方法之前设置,否则会跑出IllegalThreadStateException异常。

在守护线程中产生的新线程也是守护线程。

不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。

3、线程所操作的数据

同步问题:

4、可重入内置锁

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁。

线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁。

获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

某一个持有同步对象锁的线程可以多次进入这个同步代码块或方法。即同步对象锁可以重入!

同一个线程在调用本类中其他synchronized方法/块或父类中的synchronized方法/块时,都不会阻碍该线程地执行,因为互斥锁时可重入的。

6、Java内存模型

在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。(本地内存+共享主存)

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。

这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

而volatile关键字就是提示JVM:对于这个成员变量,不能保存它的私有拷贝,而应直接与共享成员变量交互。

volatile型变量的特殊规则:

1、保证此变量对所有线程的可见性。需要注意,volatile变量的写操作除了对它本身的读操作可见外,volatile写操作之前的所有共享变量均对volatile读操作之后的操作可见

2、禁止指令重排序优化

final域能确保初始化过程的安全性,从而可以不受限制地访问不可变对象,并在共享这些对象时无须同步

因此在编码时,不需要将long和double变量专门声明为volatile。

主内存与工作内存

Java内存模型规定所有的变量都存储在主内存中,而每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,

线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。

根据Java虚拟机规范的规定,volatile变量依然有共享内存的拷贝,但是由于它特殊的操作顺序性规定——从工作内存中读写数据前,

必须先将主内存中的数据同步到工作内存中,所有看起来如同直接在主内存中读写访问一般,因此这里的描述对于volatile也不例外

不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

对一个变量执行unlock操作之前,必须先把此变量同步回主内存(

7、轻量级同步

volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。

使用建议:在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。

由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

假如有两个线程分别读写volatile变量时,线程A写入了某volatile变量,线程B在读取该volatile变量时,便能看到线程A对该volatile变量的写入操作,

关键在这里,它不仅会看到对该volatile变量的写入操作,A线程在写volatile变量之前所有可见的共享变量,在B线程读同一个volatile变量后,都将立即变得对B线程可见。

happen—before规则介绍

其意思就是说,在发生操作B之前,操作A产生的影响都能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等,它与时间上的先后发生基本没有太大关系。

线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。

8、同步锁说明

当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,

这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。

类的每个实例都有自己的对象级别锁

访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,

因为它们获取的是各自实例的对象级别锁,相互之间没有影响。

持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非synchronized代码。

持有对象级别锁的线程会让其他线程阻塞在所有的synchronized代码外。

使用synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。

类级别锁被特定类的所有示例共享,它用于控制对static成员变量以及static方法的并发访问。具体用法与对象级别锁相似。

10、多线程环境中安全使用集合API

最初设计的Vector和Hashtable是多线程安全的。

在Collections类

中有多个静态方法,它们可以获取通过同步方法封装非同步集合而得到的集合:

public

static

List

synchronizedList(list

l)

List

list

=

Collection.synchronizedList(new

ArrayList());

注意,ArrayList实例马上封装起来,不存在对未同步化ArrayList的直接引用(即直接封装匿名实例)。

这是一种最安全的途径。如果另一个线程要直接引用ArrayList实例,它可以执行非同步修改。

从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。

、volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。

原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count

=

count+1”。

当且仅当满足以下所有条件时,才应该使用volatile变量:

1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。

2、该变量没有包含在具有其他变量的不变式中。

三、死锁问题

遵循以下原则有助于规避死锁:

1、只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;

2、尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂;

3、创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁;

四、线程通信

在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。进入wait()方法后,当前线程释放锁。

notify()

该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。

notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),

但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,

其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。

这与下面的notifyAll()方法执行后的情况不同。

notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),

变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。

如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,

直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

总结:在使用线程的等待/通知机制时,一般都要配合一个boolean变量值(或者其他能够判断真假的条件),在notify之前改变该boolean变量的值,让wait返回后能够退出while循环

(一般都要在wait方法外围加一层while循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在wait方法处。这样便保证了程序的正确性。

五、并发新特性

1、

一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,

因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool。

服务端面向连接:public

static

ExecutorService

newFixedThreadPool(int

nThreads)

Executor执行Runnable任务

Executor执行Callable任务

在Java

5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,

但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable

task)

方法来执行,

并且返回一个

Future,是表示任务等待完成的

Future。

Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是

Runnable

不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,

而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。

当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,

则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

2、自定义线程池

ThreadPoolExecutor类创建,它有多个构造方法来创建线程池

BlockingQueue

    以上《Java多线程、并发编程知识点总结》范文由一流范文网精心整理,如果您觉得有用,请收藏及关注我们,或向其它人分享我们。转载请注明出处 »一流范文网»最新范文»Java多线程、并发编程知识点总结
‖大家正在看...
设为首页 - 加入收藏 - 关于范文吧 - 返回顶部 - 手机版
Copyright © 一流范文网 如对《Java多线程、并发编程知识点总结》有疑问请及时反馈。All Rights Reserved