JAVA多线程与并发学习总结分析
http://www.djxz.com/article/40633.htm
1.计算机系统
使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行;当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。
缓存一致性:多处理器系统中,因为共享同一主内存,当多个处理器的运算任务都设计到同一块内存区域时,将可能导致各自的缓存数据不一致的情况,则同步回主内存时需要遵循一些协议。
乱序执行优化:为了使得处理器内部的运算单位能尽量被充分利用。
2.JAVA内存模型
目标是定义程序中各个变量的访问规则。(包括实例字段、静态字段和构成数组的元素,不包括局部变量和方法参数)
1.所有的变量都存储在主内存中(虚拟机内存的一部分)。
2.每条线程都由自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。
内存间交互操作:
Lock(锁定):作用于主内存中的变量,把一个变量标识为一条线程独占的状态。
Read(读取):作用于主内存中的变量,把一个变量的值从主内存传输到线程的工作内存中。
Load(加载):作用于工作内存中的变量,把read操作从主内存中得到的变量的值放入工作内存的变量副本中。
Use(使用):作用于工作内存中的变量,把工作内存中一个变量的值传递给执行引擎。
Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋值给工作内存中的变量。
Store(存储):作用于工作内存中的变量,把工作内存中的一个变量的值传送到主内存中。
Write(写入):作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。
Unlock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放出来,之后可被其它线程锁定。
规则:
1.不允许read和load、store和write操作之一单独出现。
2.不允许一个线程丢弃最近的assign操作,变量在工作内存中改变了之后必须把该变化同步回主内存中。
3.不允许一个线程没有发生过任何assign操作把数据从线程的工作内存同步回主内存中。
4.一个新的变量只能在主内存中诞生。
5.一个变量在同一时刻只允许一条线程对其进行lock操作,但可以被同一条线程重复执行多次。
6.如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行read、load操作。
7.如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作。
8.对一个变量执行unlock操作前,必须先把该变量同步回主内存中。
3.volatile型变量
1.保证此变量对所有线程的可见性。每条线程使用此类型变量前都需要先刷新,执行引擎看不到不一致的情况。
运算结果并不依赖变量的当前值、或者确保只有单一的线程修改变量的值。
变量不需要与其他的状态变量共同参与不变约束。
1.禁止指令重排序优化。普通的变量仅保证在方法执行过程中所有依赖赋值结果的地方都能获取到正确的结果。而不能保证赋值操作的顺序与程序代码中的顺序一致。
2.load必须与use同时出现;assign和store必须同时出现。
4.原子性、可见性与有序性
原子性:基本数据类型的访问读写是具备原子性的,synchronized块之间的操作也具备原子性。
可见性:指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。synchronized(规则8)和final可以保证可见性。Final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this的引用传递出去,那么在其他线程中就能看见final字段的值。
有序性:volatile本身包含了禁止指令重排序的语义,而synchronized则是由规则5获得的,这个规则决定了持有同一个所的两个同步块只能串行地进入。
5.先行发生原则
Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到。
程序次序规则:在一个线程内,按照代码控制流顺序,在前面的操作先行发生于后面的操作。
管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。
Volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
线程启动规则:Thread对象的start()方法先行发生于此线程的每个操作。
线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。
线程中断规则:对线程的interrupt()方法的调用先行发生于被中断线程的代码检测中断事件的发生。
对象终结过则:一个对象的初始化完成先行发生于它的finalize()方法的开始。
传递性:如果操作A先行发生于操作B,操作B现象发生于操作C,那么就可以得出操作A先行发生于操作C的结论。
时间上的先后顺序与先行发生原则之间基本上没有太大的关系。
6.线程实现
使用内核线程实现:
内核线程Kernel Thread:直接由操作系统内核支持的线程,这种线程由内核类完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。
轻量级进程Light Weight Process:每个轻量级进程都由一个内核线程支持。
局限性:各种进程操作都需要进行系统调用(系统调用代价相对较高,需要在用户态和内核态中来回切换);轻量级进程要消耗一定的内核资源,一次一个系统支持轻量级进程的数量是有限的。
使用用户线程实现:
用户线程:完全建立在用户空间的线程库上,系统内核不能直接感知到线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。所有的线程操作都需要用户程序自己处理。
混合实现:
将内核线程和用户线程一起使用的方式。操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁。
Sun JDK,它的Windows版和Linux版都是使用一对一的线程模型来实现的,一条Java线程映射到一条轻量级进程之中。
7.线程调度
线程调度是指系统为线程分配处理器使用权的过程:协同式、抢占式。
协同式:线程的执行时间由线程本身控制,线程把自己的工作执行完了之后,要主动通知系统切换到另一个线程上。坏处:线程执行时间不可控制。
抢占式:每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。Java使用该种调用方式。
线程优先级:在一些平台上(操作系统线程优先级比Java线程优先级少)不同的优先级实际会变得相同;优先级可能会被系统自行改变。
8.线程状态
线程状态:
新建NEW:
运行RUNNABLE:
无限期等待WAITING:等得其他线程显式地唤醒。
没有设置Timeout参数的Object.wait();没有设置Timeout参数的Thread.wait()。
限期等待TIMED_WAITING:在一定时间之后会由系统自动唤醒。
设置Timeout参数的Object.wait();设置Timeout参数的Thread.wait();Thread.sleep()方法。
阻塞BLOCKED:等待获取一个排它锁,等待进入一个同步区域。
结束TERMINATED:
9.线程安全
线程安全:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交换执行,也不需要进行额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
不可变:只要一个不可变的对象被正确地构建出来。使用final关键字修饰的基本数据类型;如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响(String类的对象)。方法:把对象中带有状态的变量都申明为final,如Integer类。有:枚举类型、Number的部分子类(AtomicInteger和AtomicLong除外)。
绝对线程安全:
相对线程安全:对这个对象单独的操作是线程安全的。一般意义上的线程安全。
线程兼容:需要通过调用端正确地使用同步手段来保证对象在并发环境中安全地使用。
线程对立:不管调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。有:System.setIn()、System.setOut()、System.runFinalizersOnExit()
10. 线程安全的实现方法
1.1.互斥同步:
在执行monitorenter指令时,首先尝试获取对象的锁,如果没有被锁定或者当前线程已经拥有了该对象的锁,则将锁计数器加1,相应的执行moniterexit时,将锁计数器减1,当计数器为0时,锁就被释放了。如果获取对象锁失败,则当前线程就要阻塞等待。
ReentrantLock相对synchronized的高级功能:
等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情。
公平锁:多个线程在等待同一个锁时,必须按照申请锁的事件顺序来一次获取锁;而非公平锁在被释放时,任何一个等待锁的线程都有机会获得锁。Synchronized中的锁是非公平锁,ReentrantLock默认也是非公平锁。
锁绑定多个条件:一个ReentrantLock对象可以同时绑定多个Condition对象。
1.2. 非阻塞同步:
基于冲突检测的乐观并发策略:先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(一般是不断的尝试,直到成功为止)。
AtomicInteger等原子类中提供了方法实现了CAS指令。
1.3.无同步方案:
可重入代码:可以在代码执行的任何时刻中断它,转而去执行另一段代码,而在控制权返回后,原来的程序不会出现任何错误。特征:不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数传入,不调用非可重入的方法等。如果一个方法,它的返回结果是可以预测的,只要出入了相同的数据,就能返回相同的结果,那它就满足可重入性的要求。
线程本地存储:如果一段代码中所需要的数据必须与其它代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。
A.ThreadLocal类
ThreadLocal:线程级别的局部变量,为每个使用该变量的线程提供一个独立的变量副本,每个线程修改副本时不影响其他线程对象的副本。ThreadLocal实例通常作为静态私有字段出现在一个类中。
11.锁优化
1.1.自旋锁
为了让线程等待,让线程执行一个忙循环(自旋)。需要物理机器有一个以上的处理器。自旋等待虽然避免了线程切换的开销,带它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效果就会非常好,反之自旋的线程只会白白消耗处理器资源。自旋次数的默认值是10次,可以使用参数-XX:PreBlockSpin来更改。
自适应自旋锁:自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
1.2.锁清除
指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行清除(逃逸分析技术:在堆上的所有数据都不会逃逸出去被其它线程访问到,可以把它们当成栈上数据对待)。
1.3.锁粗化
如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部。
HotSpot虚拟机的对象的内存布局:对象头(Object Header)分为两部分信息吗,第一部分(Mark Word)用于存储对象自身的运行时数据,另一个部分用于存储指向方法区对象数据类型的指针,如果是数组的话,还会由一个额外的部分用于存储数组的长度。
32位HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits空间中25位用于存储对象哈希码,4位存储对象分代年龄,2位存储锁标志位,1位固定为0。
HotSpot虚拟机对象头Mark Word
存储内容 |
标志位 |
状态 |
对象哈希码、对象分代年龄 |
01 |
未锁定 |
指向锁记录的指针 |
00 |
轻量级锁定 |
指向重量级锁的指针 |
10 |
膨胀(重量级锁) |
空,不记录信息 |
11 |
GC标记 |
偏向线程ID,偏向时间戳、对象分代年龄 |
01 |
可偏向 |
1.4. 轻量级锁
在代码进入同步块时,如果此同步对象没有被锁定,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储所对象目前的Mark Word的拷贝。然后虚拟机将使用CAS操作尝试将对象的Mark Word更新为执行Lock Record的指针。如果成功,那么这个线程就拥有了该对象的锁。如果更新操作失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,否则说明这个对象已经被其它线程抢占。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁。
解锁过程:如果对象的Mark Word仍然指向着线程的锁记录,那就用CAS操作把对象当前的Mark Word和和线程中复制的Displaced Mark Word替换回来,如果替换成功,整个过程就完成。如果失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
轻量级锁的依据:对于绝大部分的锁,在整个同步周期内都是不存在竞争的。
传统锁(重量级锁)使用操作系统互斥量来实现的。
1.5. 偏向锁
目的是消除在无竞争情况下的同步原语,进一步提高程序的运行性能。锁会偏向第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其它线程获取,则持有锁的线程将永远不需要再进行同步。
当锁第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为01,同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中,如果成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,都可以不进行任何同步操作。
当有另一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据所对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定或轻量级锁定状态。
12.内核态和用户态
操作系统的两种运行级别,intel cpu提供-Ring3三种运行模式。
Ring0是留给操作系统代码,设备驱动程序代码使用的,它们工作于系统核心态;而Ring3则给普通的用户程序使用,它们工作在用户态。运行于处理器核心态的代码不受任何的限制,可以自由地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。
13. 常用方法
1.1.object.wait():
在其他线程调用此对象的notify()或者notifyAll()方法,或超过指定时间量前,当前线程T等待(线程T必须拥有该对象的锁)。线程T被放置在该对象的休息区中,并释放锁。在被唤醒、中断、超时的情况下,从对象的休息区中删除线程T,并重新进行线程调度。一旦线程T获得该对象的锁,该对象上的所有同步申明都被恢复到调用wait()方法时的状态,然后线程T从wait()方法返回。如果当前线程在等待之前或在等待时被任何线程中断,则会抛出 InterruptedException。在按上述形式恢复此对象的锁定状态时才会抛出此异常。在抛出此异常时,当前线程的中断状态被清除。
只有该对象的锁被释放,并不会释放当前线程持有的其他同步资源。
1.2. object.notify()
唤醒在此对象锁上等待的单个线程。此方法只能由拥有该对象锁的线程来调用。
假如synchronized的锁对象是obj的话,wait和notify正确的使用方法是obj.wait()和obj.notify()。如果使用this作为锁,则可以直接写成wait()和notify()。如果前后使用的锁对象不一致,会发生IllegalMonitorStateException。
当有多个线程共同使用一个互斥锁时,notify()会随机选取一个执行过wait()的线程唤醒,其余会继续保持阻塞状态。如果想唤醒所有阻塞的进程,就使用到了notifyAll()。
代码如下 | 复制代码 |
package com.javaer.thread; public class Twait { public static void main(String[] args) { TestThread testThread1 = new TestThread(); TestThread testThread2 = new TestThread(); TestThread testThread3 = new TestThread(); testThread1.start(); testThread2.start(); testThread3.start(); System.out.println("主线程休眠5秒"); try { Thread.sleep(1000 * 5); } catch (InterruptedException e) { System.out.println("主线程 Interrupted"); } System.out.println("唤醒 线程Thread-0"); testThread1.resumeByNotify(); try { System.out.println("主线程再次休眠"); Thread.sleep(1000 * 5); } catch (InterruptedException e) { System.out.println("Main Thread Interrupted"); } System.out.println("唤醒所有 By NotifyAll"); testThread1.resumeByNotifyAll(); } } class TestThread extends Thread { private static Object obj = new Object(); @Override public void run() { System.out.println(getName() + " 即将进入阻塞"); synchronized (obj) { try { obj.wait(); } catch (InterruptedException e) { System.out.println(getName() + " Test Thread Interrupted"); } } System.out.println(getName() + " 被唤醒"); } public void resumeByNotify() { synchronized (obj) { obj.notify(); } } public void resumeByNotifyAll() { synchronized (obj) { obj.notifyAll(); } } } |
Thread-0 即将进入阻塞
Thread-2 即将进入阻塞
主线程休眠5秒
Thread-1 即将进入阻塞
唤醒 线程Thread-0
主线程再次休眠
Thread-0 被唤醒
唤醒所有 By NotifyAll
Thread-1 被唤醒
Thread-2 被唤醒
上面的例子,子线程启动了,就开始阻塞,然后主线程一个个的唤醒。没有线程唤醒,这个子线程将一直等待。
testThread1.resumeByNotifyAll();
注释这句话,程序将僵持在那里。传说中的僵尸。
Thread-0 即将进入阻塞
Thread-2 即将进入阻塞
主线程休眠5秒
Thread-1 即将进入阻塞
唤醒 线程Thread-0
主线程再次休眠
Thread-0 被唤醒
唤醒所有 By NotifyAll
到了这里卡住了。
结论
1.wait 当前线程因为某种原因需要进入阻塞状态,即线程暂停
2.notify 唤醒一个阻塞的线程即被wait的
3.notifyall 唤醒所有阻塞线程。
在调用wait的时候,线程自动释放其占有的对象锁,同时不会去申请对象锁。当线程被唤醒的时候,它才再次获得了去获得对象锁的权利。
1.3. Thread.sleep()
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。监控状态依然保持、会自动恢复到可运行状态,不会释放对象锁。如果任何线程中断了当前线程。当抛出InterruptedException异常时,当前线程的中断状态被清除。让出CPU分配的执行时间。
thread.join():在一个线程对象上调用,使当前线程等待这个线程对象对应的线程结束。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
thread.interrupt()
中断线程,停止其正在进行的一切。中断一个不处于活动状态的线程不会有任何作用。
如果线程在调用Object类的wait()方法、或者join()、sleep()方法过程中受阻,则其中断状态将被清除,并收到一个InterruptedException。
Thread.interrupted():检测当前线程是否已经中断,并且清除线程的中断状态(回到非中断状态)。
thread.isAlive():如果线程已经启动且尚未终止,则为活动状态。
thread.setDaemon():需要在start()方法调用之前调用。当正在运行的线程都是后台线程时,Java虚拟机将退出。否则当主线程退出时,其他线程仍然会继续执行。
14.其他
1.当调用Object的wait()、notify()、notifyAll()时,如果当前线程没有获得该对象锁,则会抛出IllegalMonitorStateException异常。
1.如果一个方法申明为synchronized,则等同于在这个方法上调用synchronized(this)。
如果一个静态方法被申明为synchronized,则等同于在这个方法上调用synchronized(类.class)。当一个线程进入同步静态方法中时,其他线程不能进入这个类的任何静态同步方法。
1.线程成为对象锁的拥有者:
1.通过执行此对象的同步实例方法
2.通过执行在此对象上进行同步的synchronized语句的正文
3.对于Class类型的对象,可以通过执行该类的同步静态方法。
1.死锁:
死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。
可能发生在以下情况:
当两个线程相互调用Thread.join();
当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必须的锁,互相等待时被阻塞就有可能出现死锁。
1.调用了Thread类的start()方法(向CPU申请另一个线程空间来执行run()方法里的代码),线程的run()方法不一定立即执行,而是要等待JVM进行调度。
run()方法中包含的是线程的主体,也就是这个线程被启动后将要运行的代码。
java多线程和并发包入门示例
一、java多线程基本入门
java多线程编程还是比较重要的,在实际业务开发中经常要遇到这个问题。 java多线程,传统创建线程的方式有两种。 1、继承自Thread类,覆写run方法。 2、实现Runnable接口,实现run方法。 启动线程的方法都是调用start方法,真正执行调用的是run方法。
参考代码如下:
package com.jack.thread;
/**
* 线程简单演示例子程序
*
* @author pinefantasy
* @since 2013-10-31
*/
public class ThreadDemo1 {
/**
* 第一种方式:继承自Thread类,覆写run方法
*/
public static class Test1Thread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Test1," + Thread.currentThread().getName() + ", i = " + i);
}
}
}
/**
* 第二种方式:实现Runnable接口,实现run方法
*/
public static class Test2Thread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Test2," + Thread.currentThread().getName() + ", i = " + i);
}
}
}
/**
* <pre>
*
* 主线程为main线程
* 分支线程为:1 2 3 三种简单实现方式
*
* @param args
*/
public static void main(String[] args) {
new Test1Thread().start();// 启动线程1
new Thread(new Test2Thread()).start();// 启动线程2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Test3," + Thread.currentThread().getName() + ", i = " + i);
}
}
}).start();// 启动线程3
}
}
二、java并发包简单入门
多个线程,统一处理同一个变量
演示代码:
package com.jack.thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 多线程对同一个变量进行操作
*
* @author pinefantasy
* @since 2013-10-31
*/
public class ThreadDemo2 {
private static int count = 0;
public static class CountThread implements Runnable {// 1.这边有线程安全问题,共享变量乱套了
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + ", count = " + count);
}
}
}
private static final Object lock = new Object();// 这边使用的lock对象
public static class Count2Thread implements Runnable {// 这边使用的是互斥锁方式
@Override
public void run() {
synchronized (lock) {// 使用互斥锁方式处理
for (int i = 0; i < 100; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ", count = " + count);
}
}
}
}
private static AtomicInteger ai = new AtomicInteger();// 这边使用的是并发包的AtomicXXX类,使用的是CAS方式:compare and swap
public static class Count3Thread implements Runnable {// AtomicInteger内部的CAS实现方式,采用的是:循环、判断、设置三部曲方式
@Override
public void run() {
for (int i = 0; i < 100; i++) {
int tmp = ai.incrementAndGet();// 采用CAS方式处理
System.out.println(Thread.currentThread().getName() + ", count = " + tmp);
}
}
}
private static volatile int countV = 0;// 定义成volatile,让多线程感知,因为值是放在主存中
public static class Count4Thread implements Runnable {// volatile定义的变量只是说放到了主存,当时++操作并不是原子操作,这个要小心
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(50);// 这边让线程休眠下,增加出错概率
} catch (InterruptedException e) {
e.printStackTrace();
}
countV++;// volatile要正确使用,不是说定义成volatile就是安全的,还是要注意++ --操作并不是原子操作
System.out.println(Thread.currentThread().getName() + ", count = " + countV);
}
}
}
/**
* 使用泛型简单编写一个测试方法
*
* @param <T>
* @param t
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InterruptedException
*/
public static <T> void testTemplate(T t) throws InstantiationException, IllegalAccessException, InterruptedException {
for (int i = 0; i < 5; i++) {
if (t instanceof Runnable) {
Class<?> c = t.getClass();
Object object = c.newInstance();
new Thread((Runnable) object).start();
}
}
}
/**
* <pre>
* 1.test1 线程不安全演示例子,count变量不能得到预期的效果
* 2.test2 在test1基础上改进的,用互斥锁sync处理
* 3.test3 在test1基础上改进的,用AtomicInteger类来实现
* 4.test4 有问题的方法,因为i++并不是原子操作,将count定义为volatile类型的
*
* @param args
* @throws InterruptedException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void main(String[] args) throws InterruptedException, InstantiationException, IllegalAccessException {
// 1.测试1
// testTemplate(new CountThread());
// 2.测试2
// testTemplate(new Count2Thread());
// 3.测试3
// testTemplate(new Count3Thread());
// 4.测试4
testTemplate(new Count4Thread());
Thread.sleep(15000);
System.out.println(count);
System.out.println(ai.get());
System.out.println(countV);
}
}
生产者-消费者模式
生产者(生成产品的线程)--》负责生成产品 消费者(消费产品的线程)--》负责消费产品
买车人、消费者。 卖车人、销售汽车的人、姑且当做生产者。 仓库、存放汽车的地方。 汽车工厂、真实生成汽车的地方。
参考代码如下:
没有加上同步机制的代码如下:
package com.jack.thread;
import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo3.CarBigHouse.Car;
/**
* 第一个版本的生产者和消费者线程
*
* @author pinefantasy
* @since 2013-11-1
*/
public class ThreadDemo3 {
/**
* 姑且卖车的当做是生产者线程
*/
public static class CarSeller implements Runnable {
private CarBigHouse bigHouse;
public CarSeller(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {// 当做生产者线程,往仓库里边增加汽车,其实是触发增加汽车
int count = bigHouse.put();
System.out.println("生产汽车-->count = " + count);
}
}
}
/**
* 姑且买车的人当做是消费者线程
*/
public static class Consumer implements Runnable {
private CarBigHouse bigHouse;
public Consumer(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {// 当做消费者线程,从仓库里边提取汽车,其实是触发,从仓库里边提取一辆汽车出来
int count = bigHouse.get();
System.out.println("消费汽车-->count = " + count);
}
}
}
/**
* 这边姑且当做是车子big house放车子的仓库房
*/
public static class CarBigHouse {
public int carNums = 0;// 这边是仓库房子中车子的数量总数
public List<Car> carList = new ArrayList<Car>();// 这边模拟用来放汽车的list
public int put() {// 提供给生产者放汽车到仓库的接口
Car car = CarFactory.makeNewCar();
carList.add(car);// 加到仓库中去
carNums++;// 总数增加1
return carNums;
}
public int get() {// 提供给消费者从这边取汽车接口
Car car = null;
if (carList.size() != 0) {// size不为空才去取车
car = carList.get(carList.size() - 1);// 提取最后一个car
carList.remove(car);// 从从库list中移除掉
carNums--;// 总数减少1
}
return carNums;
}
public static class Car {
public String carName;// 汽车名称
public double carPrice;// 汽车价格
public Car() {
}
public Car(String carName, double carPrice) {
this.carName = carName;
this.carPrice = carPrice;
}
}
}
/**
* 采用静态工厂方式创建car对象,这个只是简单模拟,不做设计模式上的过多考究
*/
public static class CarFactory {
private CarFactory() {
}
public static Car makeNewCar(String carName, double carPrice) {
return new Car(carName, carPrice);
}
public static Car makeNewCar() {
return new Car();
}
}
/**
* 第一个版本的生产者和消费者线程,没有加上同步机制的演示例子
*
* @param args
*/
public static void main(String[] args) {
CarBigHouse bigHouse = new CarBigHouse();
new Thread(new CarSeller(bigHouse)).start();
new Thread(new Consumer(bigHouse)).start();
}
}
加上互斥锁的代码如下:
package com.jack.thread;
import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo4.CarBigHouse.Car;
/**
* 第二个版本的生产者消费者线程
*
* @author pinefantasy
* @since 2013-11-1
*/
public class ThreadDemo4 {
/**
* 姑且卖车的当做是生产者线程
*/
public static class CarSeller implements Runnable {
private CarBigHouse bigHouse;
public CarSeller(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {// 当做生产者线程,往仓库里边增加汽车,其实是触发增加汽车
int count = bigHouse.put();
System.out.println("生产汽车-->count = " + count);
}
}
}
/**
* 姑且买车的人当做是消费者线程
*/
public static class Consumer implements Runnable {
private CarBigHouse bigHouse;
public Consumer(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {// 当做消费者线程,从仓库里边提取汽车,其实是触发,从仓库里边提取一辆汽车出来
int count = bigHouse.get();
System.out.println("消费汽车-->count = " + count);
}
}
}
/**
* 这边姑且当做是车子big house放车子的仓库房
*/
public static class CarBigHouse {
public int carNums = 0;// 这边是仓库房子中车子的数量总数
public List<Car> carList = new ArrayList<Car>();// 这边模拟用来放汽车的list
// 直接增加上synchronized关键字方式,成员方法,锁的是当前bigHouse对象
// 这种锁是互斥锁,方法在同一个时刻,只有一个线程可以访问到里边的代码
public synchronized int put() {// 提供给生产者放汽车到仓库的接口
Car car = CarFactory.makeNewCar();
carList.add(car);// 加到仓库中去
carNums++;// 总数增加1
return carNums;
}
public synchronized int get() {// 提供给消费者从这边取汽车接口
Car car = null;
if (carList.size() != 0) {// size不为空才去取车
car = carList.get(carList.size() - 1);// 提取最后一个car
carList.remove(car);// 从从库list中移除掉
carNums--;// 总数减少1
}
return carNums;
}
public static class Car {
public String carName;// 汽车名称
public double carPrice;// 汽车价格
public Car() {
}
public Car(String carName, double carPrice) {
this.carName = carName;
this.carPrice = carPrice;
}
}
}
/**
* 采用静态工厂方式创建car对象,这个只是简单模拟,不做设计模式上的过多考究
*/
public static class CarFactory {
private CarFactory() {
}
public static Car makeNewCar(String carName, double carPrice) {
return new Car(carName, carPrice);
}
public static Car makeNewCar() {
return new Car();
}
}
/**
* 第二个版本的生产者和消费者线程,加上了同步机制的方法
*
* @param args
*/
public static void main(String[] args) {
CarBigHouse bigHouse = new CarBigHouse();
new Thread(new CarSeller(bigHouse)).start();
new Thread(new Consumer(bigHouse)).start();
}
}
采用Object类的wait和notify方法或者notifyAll方法
// notify是唤醒其中一个在等待的线程。
// notifyAll是唤醒其他全部在等待的线程,但是至于哪个线程可以获得到锁还是要看竞争关系。
线程状态:创建、运行、阻塞、销毁状态。(阻塞情况比较多,比如等待数据IO输入,阻塞了。)
package com.jack.thread;
import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo4.CarBigHouse.Car;
/**
* 第二个版本的生产者消费者线程
*
* @author pinefantasy
* @since 2013-11-1
*/
public class ThreadDemo4 {
/**
* 姑且卖车的当做是生产者线程
*/
public static class CarSeller implements Runnable {
private CarBigHouse bigHouse;
public CarSeller(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {// 当做生产者线程,往仓库里边增加汽车,其实是触发增加汽车
int count = bigHouse.put();
System.out.println("生产汽车-->count = " + count);
}
}
}
/**
* 姑且买车的人当做是消费者线程
*/
public static class Consumer implements Runnable {
private CarBigHouse bigHouse;
public Consumer(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {// 当做消费者线程,从仓库里边提取汽车,其实是触发,从仓库里边提取一辆汽车出来
int count = bigHouse.get();
System.out.println("消费汽车-->count = " + count);
}
}
}
/**
* 这边姑且当做是车子big house放车子的仓库房
*/
public static class CarBigHouse {
public int carNums = 0;// 这边是仓库房子中车子的数量总数
public List<Car> carList = new ArrayList<Car>();// 这边模拟用来放汽车的list
public static final int max = 100;// 简单设置下,做下上限设置
private Object lock = new Object();// 采用object的wait和notify方式处理同步问题
public int put() {// 提供给生产者放汽车到仓库的接口
synchronized (lock) {
if (carList.size() == max) {// 达到了上限,不再生产car
try {
lock.wait();// 进行阻塞处理
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Car car = CarFactory.makeNewCar();
carList.add(car);// 加到仓库中去
carNums++;// 总数增加1
lock.notify();// 唤醒等待的线程
return carNums;
}
}
public int get() {// 提供给消费者从这边取汽车接口
Car car = null;
synchronized (lock) {
if (carList.size() == 0) {// 没有汽车可以用来消费
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (carList.size() != 0) {// size不为空才去取车
car = carList.get(carList.size() - 1);// 提取最后一个car
carList.remove(car);// 从从库list中移除掉
carNums--;// 总数减少1
}
lock.notify();
return carNums;
}
}
public static class Car {
public String carName;// 汽车名称
public double carPrice;// 汽车价格
public Car() {
}
public Car(String carName, double carPrice) {
this.carName = carName;
this.carPrice = carPrice;
}
}
}
/**
* 采用静态工厂方式创建car对象,这个只是简单模拟,不做设计模式上的过多考究
*/
public static class CarFactory {
private CarFactory() {
}
public static Car makeNewCar(String carName, double carPrice) {
return new Car(carName, carPrice);
}
public static Car makeNewCar() {
return new Car();
}
}
/**
* 第二个版本的生产者和消费者线程,加上了同步机制的方法
*
* @param args
*/
public static void main(String[] args) {
CarBigHouse bigHouse = new CarBigHouse();
new Thread(new CarSeller(bigHouse)).start();
new Thread(new Consumer(bigHouse)).start();
}
}