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

上传人:b****7 文档编号:10875955 上传时间:2023-02-23 格式:DOCX 页数:9 大小:20.69KB
下载 相关 举报
Java多线程并发编程知识点总结.docx_第1页
第1页 / 共9页
Java多线程并发编程知识点总结.docx_第2页
第2页 / 共9页
Java多线程并发编程知识点总结.docx_第3页
第3页 / 共9页
Java多线程并发编程知识点总结.docx_第4页
第4页 / 共9页
Java多线程并发编程知识点总结.docx_第5页
第5页 / 共9页
点击查看更多>>
下载资源
资源描述

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

《Java多线程并发编程知识点总结.docx》由会员分享,可在线阅读,更多相关《Java多线程并发编程知识点总结.docx(9页珍藏版)》请在冰豆网上搜索。

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

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

  1、线程的状态

  1.1创建线程的两种方式,接口和线程类。

利用接口的好处:

更好的体现面向对象的思想,可以避免由于Java的单继承特性而带来的局限;

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

(关注微信订阅号:

javaedu)

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

  1.3线程的中断

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

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

  线程的挂起和恢复实现的正确方法是:

通过设置标志位,让线程在安全的位置挂起

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

  1.6sleep休眠

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

  1.7stop线程停止

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

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

  1.8线程可以阻塞于四种状态:

(参考资料:

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

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

  线程阻塞与不同I/O的方式有多种。

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

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

  2、线程的种类

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

  Java中有两类线程:

UserThread(用户线程)、DaemonThread(守护线程)

  用户可以用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类中有多个静态方法,它们可以获取通过同步方法封装非同步集合而得到的集合:

  publicstaticListsynchronizedList(listl)

  Listlist=Collection.synchronizedList(newArrayList());

  注意,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。

  服务端面向连接:

publicstaticExecutorServicenewFixedThreadPool(intnThreads)

  Executor执行Runnable任务

  Executor执行Callable任务

  在Java5之后,任务分两类:

一类是实现了Runnable接口的类,一类是实现了Callable接口的类。

两者都可以被ExecutorService执行,

  但是Runnable任务没有返回值,而Callable任务有返回值。

并且Callable的call()方法只能通过ExecutorService的submit(Callabletask)方法来执行,

  并且返回一个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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 幼儿教育 > 幼儿读物

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1