01、java多线程
2019-05-15 10:02:33 0 举报
java多线程
作者其他创作
大纲/内容
6、使用阻塞队列实现线程同步
线程各个状态的转换
1、sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
概念:即有synchronized关键字修饰的方法。
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
阻塞状态
创建线程的三种方法的对比
信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身
Thread
例子和注意事项://需要同步的变量加上volatileprivate volatile int account = 100;注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法.用final域,有锁保护的域和volatile域可以避免非同步的问题。
例子与注意事项:ThreadLocal 类的常用方法 ThreadLocal() : 创建一个线程本地变量 get() : 返回此线程局部变量的当前线程副本中的值 initialValue() : 返回此线程局部变量的当前线程的\"初始值\" set(T value) : 将此线程局部变量的当前线程副本中的值设置为value注:ThreadLocal与同步机制 a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 b.前者采用以\"空间换时间\"的方法,后者采用以\"时间换空间\"的方式
用户输入结束sleep结束join的线程结束
等待队列
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的战占有
同步阻塞:线程在获得synchronized同步锁失败(因为同步锁被其他线程占用)
8、套接口(Socket)
5、sleep和wait最大的区别是:
代码:public class DeprecatedSuspendResume extends Object implements Runnable{ \t//volatile关键字,表示该变量可能在被一个线程使用的同时,被另一个线程修改\tprivate volatile int firstVal;\tprivate volatile int secondVal; \t//判断二者是否相等\tpublic boolean areValuesEqual(){\t\treturn ( firstVal == secondVal);\t} \tpublic void run() {\t\ttry{\t\t\tfirstVal = 0;\t\t\tsecondVal = 0;\t\t\tworkMethod();\t\t}catch(InterruptedException x){\t\t\tSystem.out.println(\"interrupted while in workMethod()\");\t\t}\t} \tprivate void workMethod() throws InterruptedException {\t\tint val = 1;\t\twhile (true){\t\t\tstepOne(val);\t\t\tstepTwo(val);\t\t\tval++;\t\t\tThread.sleep(200); //再次循环钱休眠200毫秒\t\t}\t}\t\t//赋值后,休眠300毫秒,从而使线程有机会在stepOne操作和stepTwo操作之间被挂起\tprivate void stepOne(int newVal) throws InterruptedException{\t\tfirstVal = newVal;\t\tThread.sleep(300); //模拟长时间运行的情况\t} \tprivate void stepTwo(int newVal){\t\tsecondVal = newVal;\t} \tpublic static void main(String[] args){\t\tDeprecatedSuspendResume dsr = new DeprecatedSuspendResume();\t\tThread t = new Thread(dsr);\t\tt.start(); \t\t//休眠1秒,让其他线程有机会获得执行\t\ttry {\t\t\tThread.sleep(1000);} \t\tcatch(InterruptedException x){}\t\tfor (int i = 0; i < 10; i++){\t\t\t//挂起线程\t\t\tt.suspend();\t\t\tSystem.out.println(\"dsr.areValuesEqual()=\" + dsr.areValuesEqual());\t\t\t//恢复线程\t\t\tt.resume();\t\t\ttry{ \t\t\t\t//线程随机休眠0~2秒\t\t\t\tThread.sleep((long)(Math.random()*2000.0));\t\t\t}catch(InterruptedException x){\t\t\t\t//略\t\t\t}\t\t}\t\tSystem.exit(0); //中断应用程序\t}}
线程间通信
多线程的使用
为什么要使用线程同步
4、线程池只能放入实现Runnable或者Callable类线程,不能直接放入继承Thread的类
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
2、通过继承Thread类本身:创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
4、使用重入锁实现线程同步
记录
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
参考网址
描述:由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。 代码如: public synchronized void save(){} 注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
3、yield: yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
各个状态的转换
01、新建状态
具体的方法:下面我们给出不用上述两个方法来实现线程挂起和恢复的策略——设置标志位。通过该方法实现线程的挂起和恢复有一个很好的地方,就是可以在线程的指定位置实现线程的挂起和恢复,而不用担心其不确定性。
使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低设计的。
描述:被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 代码如: synchronized(object){ } 注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
synchronized
线程的优先级
3、信号(Signal)
class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo( String name) { threadName = name; System.out.println(\"Creating \" + threadName ); } public void run() { System.out.println(\"Running \" + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println(\"Thread: \" + threadName + \
创建线程的方法
等待用户输入Thread.sleep()t2.join()
另一个问题是,当在某个线程上调用stop()方法时,线程释放它当前持有的所有锁,持有这些锁必定有某种合适的理由——也许是阻止其他线程访问尚未处于一致性状态的数据,突然释放锁可能使某些对象中的数据处于不一致状态,而且不会出现数据可能崩溃的任何警告。
命名管道克服了管道没有名字的限制,除具有管道所具备的的功能外,它还允许无亲缘关系进程的通信
常见函数
更为一般的进程间通信机制,可用于不同机器之间的进程间通信
锁池状态
主要作为进程间以及同一进程不同线程之间的同步手段
03、运行状态
2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
解决挂起时线程不安全的方法:通过设置标志位,让线程在安全的位置挂起
参考网址:https://www.cnblogs.com/XHJT/p/3897440.html
1、适合多个相同的程序代码的线程去处理同一个资源
7、信号量(semaphore)
class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo( String name) { threadName = name; System.out.println(\"Creating \" + threadName ); } public void run() { System.out.println(\"Running \" + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println(\"Thread: \" + threadName + \
4、消息(Message)队列
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
消息队列是消息的链接表,包括Posix消息队列system V消息队列
如果是使用stop时,会导致两个问题
概念: a.volatile关键字为域变量的访问提供了一种免锁机制 b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
7、使用原子变量实现线程同步
1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
一个问题是,当线程终止时,很少有机会执行清理工作;
结束
2、命名管道(named pipe)
例子和注意事项:其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。AtomicInteger类常用方法:AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicIntegeraddAddGet(int dalta) : 以原子方式将给定值与当前值相加get() : 获取当前值
拿到对象的锁标记
解决问题的方法: 终止线程的替代方法:同样是使用标志位,通过控制标志位来终止线程。
2、同步代码块
run()结束、main结束
02、就绪状态
https://blog.csdn.net/evankaka/article/details/44153709#t9
Java 多线程编程
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度
4、interrupt():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
如果就绪状态的线程获得CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态
线程控制:挂起、停止和恢复
05、死亡状态
1、优先级的取值:1到10;2、默认优先级:5;3、设置优先级不能够保证一定会优先执行,还要取决于平台;注意:优先级最好是使用Thread类里面的:MIN_PRIORITYNORM_PRIORITYMAX_PRIORITY因为其他的优先级有可能不存在
概念:如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
线程死锁
参考网址:https://blog.csdn.net/ns_code/article/details/17095733
概念:需要使用线程同步的根本原因在于对普通变量的操作不是原子的。那么什么是原子操作呢?原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。
1、如果是重复运行的话会报java.lang.IllegalThreadStateExecption错误
3、.使用特殊域变量(volatile)实现线程同步
2、继承Thread和实现Runnable的区别
04、阻塞状态
线程同步
3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
有效利用多线程的关键是理解程序是并发执行而不是串行执行的。
2、join:join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
时间片用完Thread.yield()
2、可以避免java中单继承的限制
1、通过实现Runnable接口:创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。
new Thread()
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或者获得设备资源可以重新进入就绪状态。可以分为三种
初始状态
o.notify()o.notifyAll()wait时间到
例子与注意事项:ReenreantLock类的常用方法有: ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁 注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 注:关于Lock对象和synchronized关键字的选择: a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
运行中
wait()睡眠时,释放对象锁
概念: 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
3、通过Callable和Future创建线程:1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
5、使用局部变量实现线程同步
线程的几个主要概念
概念:前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步 LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。
1、管道(Pipe)
o.wait()
等待阻塞:使用wait(),使线程进入到等待阻塞状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态
其他阻塞:通过调用线程的sleep()或者join()发出来I/O请求时,线程就会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新进入就绪状态
6、内存映射(mapped memory)
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+\" 的循环变量i的值\
使用new关键字和Thread类或者其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start()这个线程
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路
例子和注意事项:LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加一个元素,如果队列满则阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时: add()方法会抛出异常 offer()方法返回false put()方法会阻塞
死锁产生的条件
可运行
管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
挂起:如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件——其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁。另外,在长时间计算期间挂起线程也可能导致问题。
sleep()睡眠时,保持对象锁,任然占有该锁;
概念:即有synchronized关键字修饰的语句块。
t.start()
1、同步方法
5、共享内存
一个线程的生命周期
具体代码:public class AlternateSuspendResume extends Object implements Runnable { \tprivate volatile int firstVal;\tprivate volatile int secondVal;\t//增加标志位,用来实现线程的挂起和恢复\tprivate volatile boolean suspended; \tpublic boolean areValuesEqual() {\t\treturn ( firstVal == secondVal );\t} \tpublic void run() {\t\ttry {\t\t\tsuspended = false;\t\t\tfirstVal = 0;\t\t\tsecondVal = 0;\t\t\tworkMethod();\t\t} catch ( InterruptedException x ) {\t\t\tSystem.out.println(\"interrupted while in workMethod()\
内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
OS调度选中
收藏
0 条评论
回复 删除
下一页