您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Java多线程学习笔记(全)
 
作者:小徐 来源:新浪微博 发布于 2015-8-27
   次浏览      
 

一.线程的创建和启动

java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序流的代码)。Java使用run方法来封装这样一段程序。

1.继承Thread类创建线程类

/**继承Thread来创建线程类*/  
public class FirstThread extends Thread {
private int i;
//重写run方法,run方法的方法体就是线程执行体
public void run() {
for(;i<10;i++){
System.out.println(this.getName()+":"+i);
}
}
public static void main(String []args){
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" .."+i);
if(i==10){
System.out.println("--------------------------------------------");
new FirstThread().start();
new FirstThread().start();
System.out.println("---------------------------------------------");
}
}
}
}
结果:红色部分每次运行都不一致,因为多线程也是并发的
main ..0
main ..1
main ..2
main ..3
main ..4
main ..5
main ..6
main ..7
main ..8
main ..9
main ..10
--------------------------------------------
Thread-0:0
---------------------------------------------
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-0:1
Thread-1:4
Thread-1:5
main ..11
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-0:2
Thread-0:3
main ..12
main ..13
......

总结 :从上面结果可以看出Thread-0和Thread-1两条线程输出的i变量都不连续(注意:i变量是FirestThread的实例属性,而不是局部变量,但因为程序每次创建线程都会创建一个FirstThread对象,所以Thread-0和Thread-1不能共享该实例属性)。

使用继承Thread类的方法来创建线程类,多条线程之间无法共享线程类的实例变量。

2.实现Runnable接口创建线程类

public class SecondThread implements Runnable {  
private int i;
public void run() {
for(;i<20;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String [] args){
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" .."+i);
if(i==10){
SecondThread st=new SecondThread();
//通过new Thread( Runable target,String name)来创建新线程
new Thread(st,"线程1").start();
new Thread(st,"线程2").start();
}
}
}
结果:红色部分每次运行都不一致,因为多线程也是并发的
main ..0
main ..1
main ..2
main ..3
main ..4
main ..5
main ..6
main ..7
main ..8
main ..9
main ..10
--------------------------------------------
线程1:0
--------------------------------------------
线程1:1
线程2:1
线程2:3
main ..11
线程2:4
线程2:5
线程2:6
线程1:2
线程2:7
线程2:9
线程2:10
线程2:11
线程2:12
线程2:13
main ..12
线程2:14
线程2:15
线程2:16
线程2:17
线程1:8
线程2:18
main ..13
main ..14
线程1:19
main ..15
main ..16
main ..17
。。。。

总结:根据源代码中Thread类构造方法 Ruanalbe接口对象target只能作为参数传递到Thread构造方法中,所以多个线程可以共用一个Runnable对象,因为都用同一个Runnable对象所以在Runnable实现类的实例变量也可以共享了。

所以Runable非常适合多个相同线程来处理同一份资源的情况。

二.线程的生命周期

1.New新建 :当线程被创建时,该线程处于新建状态,此时它和其他java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化了其成员变量的值。(此时的线程没有表现出任何表现出任何线程的动态特征,程序也不会执行线程的线程执行体)new Thread()||new Thread(Runnable target,String name)。

2.Runnable就绪:就绪也就是说启动线程,但是启动线程使用start方法,而不是run方法!永远不要调用线程对象的run()方法!调用start方法来启动线程,系统会将该run方法当成线程执行体来处理。如果直接调用线程对象的run方法。则run方法会立即执行,且在这个run方法的执行体未执行结束前其他线程无法并发执行(即系统会将run方法当做一个普通对象的普通方法,而不是线程执行体对待)

附1:如果有一个主线程,一个子线程。当根据逻辑代码该调用子线程时不一定会立即调用,为了想在子线程start()后立即调用子线程,可以考虑使用Thread.sleep(1),这样会让当前线程(主线程)睡眠1毫秒,因为cpu在这1毫秒中是不会休息的,这样就会去执行一条处于就绪状态的线程。

附2:不能对已经处于就绪状态的线程,再次使用start()

3.Running 运行:当处于就绪状态时,该线程获得cpu,执行体开始运行,就处于运行状态了。

4.Blocked 阻塞:线程不可能一直处于运行状态(线程执行体足够短,瞬间就可以完成的线程排除),线程会在运行过程中需要被中断,因为是并发,目的是会让其他线程获得执行的机会,线程的调度细节取决于OS采用的策略。(抢占式调度xp win7 linux unix..)。如果是一些特殊的小型设备可能采用 协作式调度(只有线程自己调用它的sleep()或yield()才会放弃所占用的资源)。

5.Dead死亡:根据上图所示。测试测试某条线程是否已经死亡,可以调用线程对象的isAlive()方法,当线程处于就绪,运行,阻塞时,返回true。线程处于新建,死亡时返回false。

不能对已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,是不能再次作为线程执行的。

当主线程结束时候,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受到主线程的影响。

三.控制线程

1.join线程:

让一个线程等待另一个线程完成的方法:join()。当在某个程序执行流中调用其他线程的join()方法,那该执行流对应的线程就会阻塞,知道被join()加入的join线程完成为止。join方法通常有使用线程的程序调用,将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用 主线程来进一步操作(Thread t=new Thread();t.start();t.join简单来说就是加入到t线程。等t线程执行完成后才会返回出来执行线程。)

Join方法有三种重用形式:

Join():等待被join的线程执行完成

Join(long millis):等待join线程的时间最长为millis毫秒,如果在这个时间内,被join的线程还没有执行结束则不再等待)

Join(long millis,int nanos)千分之一毫秒(不用)

public class JoinThread implements Runnable{  
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String [] args) throws InterruptedException{
//实例化一个Runnable
JoinThread jt=new JoinThread();
//创建一个线程
new Thread(jt).start();
for(int i=0;i<10;i++){
if(i==3){
Thread th=new Thread(jt);
//启动第二个线程
th.start();
//main的线程中调用了th线程的join方法
//让第二个线程执行完成后再执行main
th.join();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
结果:
Thread-0:0
Thread-0:1
Thread-0:2
main:0
main:1
Thread-0:3
main:2
Thread-0:4
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
main:3
main:4
main:5
main:6
main:7
main:8
main:9

2.后台线程:

public class DaemonThread implements Runnable{  
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String [] args){
//要将前台线程转换成后台线程,需要在该线程刚新建还未start()之前转换。main线程也是前台线程
//所有前台线程死亡时,后台线程也就随之死亡。
DaemonThread dt=new DaemonThread();
Thread td=new Thread(dt,"线程1");
System.out.println("main方法是否是后台线程"+Thread.currentThread().isDaemon());
System.out.println("td线程最初是否是后台线程"+td.isDaemon());
//指定td为后台线程
td.setDaemon(true);
System.out.println("td线程执行setDaemon方法后是否是后台线程"+td.isDaemon());
//就绪启动后台线程
td.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
结果:只要前台线程结束,后台线程也会随之结束,并不是马上结束
main方法是否是后台线程false
td线程最初是否是后台线程false
td线程执行setDaemon方法后是否是后台线程true
main 0
main 1
线程1:0
线程1:1
main 2
线程1:2
线程1:3
main 3
线程1:4
线程1:5
main 4
线程1:6
线程1:7
线程1:8
线程1:9
线程1:10
线程1:11
线程1:12
线程1:13

3.线程睡眠:sleep

/** 
* 线程睡眠:sleep有两种重载形式:
* static void sleep(long millis)
* static void sleep(long millis,int nanos)
*
*/
public class SleepThread {
public static void main(String [] args) throws InterruptedException{
for(int i=0;i<5;i++){
System.out.println("线程:"+Thread.currentThread().getName()+"当前时间:"+new Date());
//让当前线程暂停2秒
Thread.sleep(2000);
}
}
}
结果:
线程:main当前时间:Fri Nov 04 18:51:33 CST 2011
线程:main当前时间:Fri Nov 04 18:51:35 CST 2011
线程:main当前时间:Fri Nov 04 18:51:37 CST 2011
线程:main当前时间:Fri Nov 04 18:51:39 CST 2011
线程:main当前时间:Fri Nov 04 18:51:41 CST 2011

4.线程让步(yield)

/** 
* yield()方法是一个和sleep方法有点类似的静态方法。yield也可以让当前正在执行的线程暂停
* 但它不会阻塞该线程,它只是将该线程转入就绪状态。yield只是让当前线程暂停一会儿,让系统的
* 调度器重新调度一次(完全可能的情况是:当一个线程调用了yield方法暂停之后,线程调度器又马上
* 将其调度出来重新执行。)
* 实际上,当前线程调用了yield方法后,只有优先级和当前线程相同,甚至优先级高于当前线程的处于
* 就绪状态的线程才会获得执行机会。
*
*/
public class YieldThread implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if(i==20){
Thread.yield();
}
}
}
public static void main(String [] args){
//启动第一条子线程
Thread td1=new Thread(new YieldThread(),"线程1");
//最高级
//td1.setPriority(Thread.MAX_PRIORITY);
//启动第二条子线程
Thread td2=new Thread(new YieldThread(),"线程2");
//最低级
td2.setPriority(Thread.MIN_PRIORITY);
td1.start();
td2.start();
System.out.println(Thread.currentThread().getName());
}
}

总结:sleep和yield区别

A.sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。而yield只会给优先级>=当前优先级的线程执行机会

B.Sleep方法会将线程转入阻塞状态,知道经过阻塞时间才会转入就绪状态。而yield是不会将线程转入阻塞状态的,它只是强制当前线程进入就绪状态。

C.Sleep会抛出InterruptedException异常。而yield没有声明任何异常

D.Sleep方法比yield方法有更好的移植性。

E.通常不依靠yield来控制并发线程控制

四.多线程的同步

以一个取钱列子来分析:(用户登录那些省略)

Accout类:

/**银行取钱,账户类*/  
public class Accout {
//账户编号
private String accoutNo;
//账户余额
private double balance;
//账户名称
private String accoutName;
public Accout(){
super();
}
public Accout(String accoutNo,String accoutName, double balance) {
super();
this.accoutNo = accoutNo;
this.balance = balance;
this.accoutName=accoutName;
}
public String getAccoutNo() {
return accoutNo;
}
public void setAccoutNo(String accoutNo) {
this.accoutNo = accoutNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public String getAccoutName() {
return accoutName;
}
public void setAccoutName(String accoutName) {
this.accoutName = accoutName;
}
//根据accoutNohe来计算Accout的hashcode和判断equals
@Override
public int hashCode() {
return accoutNo.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj!=null&&obj.getClass()==Accout.class){
Accout target=(Accout)obj;
return target.getAccoutNo().equals(accoutNo);
}
return false;
}
}

DrawThread类:

/**取钱的线程类*/  
public class DrawThread implements Runnable{
//模拟用户账户
private Accout accout;
//当前取钱线程所希望取得值
private double drawAmount;
public DrawThread(Accout accout, double drawAmount) {
super();
this.accout = accout;
this.drawAmount = drawAmount;
}
//如果多个线程修改同一个共享数据时,会发生数据安全问题
public void run() {
//账户余额大于取款金额时
if(accout.getBalance()>=drawAmount){
//取款成功
System.out.println(Thread.currentThread().getName()+ accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);
//修改余额
accout.setBalance(accout.getBalance()-drawAmount);
System.out.println("当前余额为:"+accout.getBalance());
}
//账户金额不够时
else{
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
}
}
}

TestDraw测试类:

public class TestDraw {  
public static void main(String[]args) throws InterruptedException{
//创建一个用户
Accout acct=new Accout("123456", "小明", 1000);
//模拟四个线程同时操作
DrawThread dt=new DrawThread(acct,600);
//DrawThread dt1=new DrawThread(acct,800);
Thread th1=new Thread(dt,"线程1");
Thread th2=new Thread(dt,"线程2");
Thread th3=new Thread(dt,"线程3");
Thread th4=new Thread(dt,"线程4");
th4.join();
th1.start();
th2.start();
th3.start();
th4.start();
}
}

1.同步代码块

Java多线程支持引入了同步监视器来解决多线程安全,同步监视器的常用方法就是同步代码块:

Synchronized(obj){  
//...同步代码块
}

括号中的obj就是同步监视器:上面的语句表示:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。这就意味着任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。

虽然java中对同步监视器使用的对象没有任何要求,但根据同步监视器的目的:阻止两条线程对同一个共享资源进行并发访问。所以一般将可能被并发访问的共享资源充当同步监视器。

修改后如下:

public void run() {  
synchronized (accout) {
//账户余额大于取款金额时
if(accout.getBalance()>=drawAmount){
//取款成功
System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);
//修改余额
accout.setBalance(accout.getBalance()-drawAmount);
System.out.println("当前余额为:"+accout.getBalance());
}
//账户金额不够时
else{
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
}
}
}

2.同步方法

(synchronized可以修饰方法,代码块。不能修饰属性和构造方法)

除了同步代码块外还可以使用synchronized关键字来修饰方法,那么这个修饰过的方法称为同步方法。对于同步方法来说,无需显式指定同步监视器,同步方法的同步监视器是this,也就是该对象本身,也就是上面TestDraw中定义的Accout类型的acct。

public void run() {  
draw();
}
public synchronized void draw(){
if(accout.getBalance()>=drawAmount){
//取款成功
System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);
//修改余额
accout.setBalance(accout.getBalance()-drawAmount);
System.out.println("当前余额为:"+accout.getBalance());
}
//账户金额不够时
else{
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
}
}

这里最好是将draw()方法写到Accout中,而不是像之前将取钱内容保存在run方法中,这种做法才更符合面向对象规则中的DDD(Domain Driven Design领域驱动设计)

对于可变类的同步会降低程序运行效率。不要对线程安全类德所有方法进行同步,只对那些会改变共享资源的方法同步。

单线程环境(可以使用线程不安全版本保证性能) 多线程环境(线程安全版本)

2.1同步监视器的锁定什么时候释放

A.当前线程的同步方法,同步块执行结束。当前线程释放同步监视器

B.在同步方法,块中遇到break,return终止了该代码块,方法.释放

C.在代码块,方法中出现Error,Exception

D.执行同步时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,释放

2.2同步监视器的锁定在以下情况不会被释放

A.执行同步时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,不会释放

B.执行同步时,其他线程调用了该线程的suspend方法将该线程挂起,不会释放(但是尽量避免使用suspend和resume来控制线程,容易导致死锁)

3.同步锁(Lock)

在jdk1.5后,java除了上面两种同步代码块和同步方法之外,还提供了一种线程同步机制:它通过显示定义同步锁对象来实现同步,在这种机制下,同步锁应该使用Lock对象充当。

Lock是控制多个线程对共享资源进行访问的工具,每次只能有一个线程对Lock对象枷锁,线程开始访问共享资源之前应先获得Lock对象。(特例:ReadWriteLock锁可能允许对共享资源并发访问)。在实现线程安全控制中,通常喜欢使用可重用锁(ReentrantLock),使用该Lock对象可以显示的加锁,释放锁。

CODE:

 //声明锁对象  
private final ReentrantLock relock=new ReentrantLock();
public void run(){
draw();
}
public void draw() {
//加锁
relock.lock();
try{
//账户余额大于取款金额时
if(accout.getBalance()>=drawAmount){
//取款成功
System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:吐出钞票:"+drawAmount);
//修改余额
accout.setBalance(accout.getBalance()-drawAmount);
System.out.println("当前余额为:"+accout.getBalance());
}
//账户金额不够时
else{
System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
}
}
//释放锁
finally{
relock.unlock();
}}

总结:

同步方法和同步代码块使用与共享资源相关的,隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。

虽然同步方法,代码块的范围机制使多线程安全编程非常方便,还可以避免很多涉及锁的常见编程错误,但有时也需要以更灵活的方式使用锁。Lock提供了同步方法,代码块中没有的其他功能(用于非块结构的tryLock方法,获取可中断锁lockInterruptibly方法,获取超时失效锁的tryLock(long,TimeUnit)方法)。

ReentrantLock锁具有重入性,即线程可以对它已经加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock方法的嵌套调用,线程每次调用lock()加锁后,必须显示的调用unlock()释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

4.死锁

当两个线程相互等待对方释放同步监视器的时候就会发生死锁,一旦出现死锁,整个程序既不会发生任何异常,也不会有任何提示,只是所有线程处于阻塞状态,无法继续。

五.线程通信

线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,可以通过以下方法来保证线程协调运行.

1.线程协调运行

如果对于一些方法是用同步方法或者同步代码块,那么可以调用Object类提供的wait(),notify(),notifyAll()。这三个不属于Thread,属于Object类,但必须由同步监视器来调用(同步方法的监视器是this:则需this.wait()....,同步代码块的监视器是括号中的obj.wait());

2.使用条件变量来控制协调

如果程序没有使用sychronized来保证同步,可以使用Lock来保证同步,则系统中就不存在隐式的同步监视器对象,也就不能使用wait,notify,notifyAll来协调了。

private final Lock lock=new ReentrantLock();  
private final Condition cond=lock.newCondition();

通过上面两行代码,条件Condition实际是绑定在一个Lock对象上的。

相对应的Condition类也有三个方法:await(),signal(),signalAll()

Account账号类:

代码:

/** 账户类,用面向对象的DDD设计模式来设计 */  
/*
* DDD领域驱动模式,即将每个类都认为是一个完备的领域对象, 例如Account代表账户类,那么就应该提供用户账户的相关方法(存,取,转),而不是将
* setXXX方法暴露出来任人操作。 只要设计到DDD就需要重写equals和hashcode来判断对象的一致性
*/
public class Account {
// 账户编码
private String accountNo;
// 账户余额
private double balance;
// 标示账户是否已有存款(此项目为了测试存入款就需要马上取出)
private boolean flag = false;
// private final Lock lock=new ReentrantLock();
// private final Condition cond=lock.newCondition();
public Account() {
super();
}
public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
// 取款(利用同步方法)
public synchronized void draw(double drawAmount) {
// 如果flag为假,没人存款进去,取钱方法(利用wait)阻塞(wait阻塞时,当前线程会释放同步监视器)
try {
if (!flag) {
this.wait();//条件 cond.await();
}
//否则执行取钱
else
{ // System.out.println("账户余额:"+balance);
System.out.println(Thread.currentThread().getName()+"---->取钱:"+drawAmount);
balance-=drawAmount;
System.out.println("账户余额: "+balance);
//设置flag(限定一个操作只能取一次钱)
flag=false;
//唤醒其他wait()线程
this.notifyAll();//cond.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存款
public synchronized void deposit(double depositAmount){
//如果flag为真,证明有人存钱了,存钱阻塞
try {
if (flag) {
this.wait(); //cond.await();
}
//否则执行存款
else
{ // System.out.println("账户余额:"+balance);
System.out.println(Thread.currentThread().getName()+"---->存钱:"+depositAmount);
balance+=depositAmount;
System.out.println("账户余额: "+balance);
//设置flag(限定一个操作只能取一次钱)
flag=true;
//唤醒其他wait()线程
this.notifyAll(); //cond.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// DDD设计模式重写equals和hashcode(判断用户是否一致, 只需要判断他们的账号编码就可以了,不需要再判断整个对象,提高性能)
@Override
public int hashCode() {
return accountNo.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj != null && obj.getClass() == Account.class) {
Account account = (Account) obj;
return account.getAccountNo().equals(accountNo);
}
return false;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}

取钱线程:

public class DrawThread implements Runnable {  
/*
* 模拟用户
*/
private Account account;
//用户取钱数
private double drawAmount;
public DrawThread(Account account, double drawAmount) {
super();
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
//重复10次取钱操作
for(int i=0;i<10;i++){
account.draw(drawAmount);
}
}
}

存钱线程:

public class DepositThread  implements Runnable{  
/*
* 模拟用户
*/
private Account account;
//用户存钱数
private double depositAmount;
public DepositThread(Account account, double depositAmount) {
super();
this.account = account;
this.depositAmount = depositAmount;
}
@Override
public void run() {
//重复10次存钱操作
for(int i=0;i<10;i++){
account.deposit(depositAmount);
}
}
}

测试类:

public class Test {  
public static void main(String []args){
//创建一个用户没余额,等待先存款后取钱
Account acct=new Account("123张",0);
//取款800
new Thread(new DrawThread(acct,800),"取款者").start();
//存款2个人
new Thread(new DepositThread(acct,800),"存款者甲").start();
new Thread(new DepositThread(acct,800),"存款者乙").start();
new Thread(new DepositThread(acct,800),"存款者丙").start();
}
}
结果:
存款者甲---->存钱:800.0
账户余额: 800.0
取款者---->取钱:800.0
账户余额: 0.0
存款者丙---->存钱:800.0
账户余额: 800.0
取款者---->取钱:800.0
账户余额: 0.0
存款者甲---->存钱:800.0
账户余额: 800.0
取款者---->取钱:800.0
账户余额: 0.0
存款者丙---->存钱:800.0
账户余额: 800.0
取款者---->取钱:800.0
账户余额: 0.0
存款者甲---->存钱:800.0
账户余额: 800.0
取款者---->取钱:800.0
账户余额: 0.0
存款者丙---->存钱:800.0
账户余额: 800.0

但根据上面情况来看,显示用户被阻塞无法继续向下执行,这是因为存钱有三个线程 共有3*10=30次操作,而取钱只有10次,所以阻塞。
阻塞和死锁是不一致的,这里阻塞只是在等待取钱。。。

3.使用管道流(通信)

上面的1,2两种线程操作,与其称为线程间的通信,不如称为线程之间协调运行的控制策略还要恰当些。如果需要在两条线程之间惊醒更多的信息交互,则可以考虑使用管道流进行通信。

管道流有3中形式:

1.字节流:PipedInputStream,PipedOutputStream

2.字符流:PipedReader,PipedWriter

3.新IO的管理Channel:Pipe.SinkChannel,Pipe.SourceChannel

使用管道的步骤:

1.new创建管道输入输出流

2.使用管道输入或输出流的connect方法连接这两个输入输出流

3.将两个管道流分别传入两个线程

4.两个线程分别依赖各自的流来进行通信

但是因为两个线程属于同一个进程,它们可以非常方便的共享数据,利用共享这个方式才应该是线程之间进行信息交流的最好方式,而不是使用管道流。如果是操作诸如聊天室那样的话,用管道通信效果会好些,共享资源的话,性能太低,出错概率高。

CODE:

/**读取管道中的信息线程*/  
public class ReaderThread implements Runnable {
private PipedReader pr;
private BufferedReader br;
public ReaderThread() {
super();
}
public ReaderThread(PipedReader pr) {
super();
this.pr = pr;
//包装管道流
this.br=new BufferedReader(pr);
}
public void run(){
String buffer=null;
System.out.println(Thread.currentThread().getName());
try{
//开始逐行读取管道流数据(假定管道流数据时字符流)
System.out.println("------打印管道中的数据-------");
while((buffer=br.readLine())!=null){
System.out.println(buffer);
}
}
catch(IOException e){
e.printStackTrace();
}
finally{
try {
if(br!=null)
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**像管道流中写入数据*/  
public class WriterThread implements Runnable {
//定义一个数组来充当向管道中输入数据
String []str=new String[]{"1.www.csdn.net论坛","2.www.google.com谷歌", "3.www.hibernate.orgHibernate","4.www.xiami.com虾米"};
private PipedWriter pw;
public WriterThread() {
}
public WriterThread(PipedWriter pw) {
this.pw = pw;
}
public void run(){
System.out.println(Thread.currentThread().getName());
try{
//向管道流中写入数据,以供读取
for(int i=0;i<100;i++){
pw.write(str[i%4]+"\n");
}
}
catch(IOException e){
e.printStackTrace();
}
finally{
try {
if(pw!=null){
pw.close();
}
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

public class TestPiped {  
public static void main(String[] args) {
PipedReader pr = null;
PipedWriter pw = null;
try {
pw = new PipedWriter();
pr = new PipedReader();
// 链接管道
pr.connect(pw);
new Thread(new ReaderThread(pr),"读取管道线程").start();
new Thread(new WriterThread(pw),"写入管道线程").start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
读取管道线程
------打印管道中的数据-------
写入管道线程
1.www.csdn.net论坛
2.www.google.com谷歌
3.www.hibernate.orgHibernate
4.www.xiami.com虾米
1.www.csdn.net论坛
2.www.google.com谷歌
3.www.hibernate.orgHibernate
4.www.xiami.com虾米
1.www.csdn.net论坛
.....(一共100条)

六.线程组(ThreadGroup)

1.线程组介绍:

线程组可以对一批线程进行分类管理,Java也允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程。用户创建的所有线程都属于指定线程组,如果没有显示指定线程属于哪个线程组,那么这个线程属于默认线程组。在默认情况下,子线程和创建它的父线程处于同一个线程组内:例如A创建B线程,B没有指定线程组,那么A和B属于同一个线程组。

一旦某个线程加入到指定线程组,那么该线程就一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组(中途不能改变线程组,所以Thread类只有getThreadGroup()方法来获得线程组,而没有set方法。)。

public final ThreadGroup getThreadGroup() {  
return group;
}

Thread类中构造方法:

其中参数的个数根据情况而定,init中会根据参数个数而变,没这个参数的就直接null long类型就0.

public Thread(ThreadGroup group, Runnable target, String name,  
long stackSize) {
init(group, target, name, stackSize);
}

ThreadGroup类中的构造方法:

//不属于任何一个线程组,是一个必须的用来创建系统线程的组  
/**
* Creates an empty Thread group that is not in any Thread group.
* This method is used to create the system Thread group.
*/
private ThreadGroup() { // called from C code
this.name = "system";
this.maxPriority = Thread.MAX_PRIORITY;
this.parent = null;
}
------------------------------------------------------
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
parent.add(this);
}
//指定线程组名创建一个线程组
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
//指定的父线程和线程组名来创建一个线程组
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}

看出上面两个public构造器都需要指定一个名字给线程组,所以线程组总是具有一个字符串名字,该名称可调用ThreadGroup的getName()获得,但不允许改变线程组的名字。

源码setMaxPriority(int pri)

public final void setMaxPriority(int pri) {  
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
checkAccess();
if (pri < Thread.MIN_PRIORITY || pri > Thread.MAX_PRIORITY) {
return;
}
maxPriority = (parent != null) ? Math.min(pri, parent.maxPriority) : pri;
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
}
for (int i = 0 ; i < ngroupsSnapshot ; i++) {
groupsSnapshot[i].setMaxPriority(pri);
}
}

例子:

/**测试线程组*/  
public class TestThread implements Runnable {
//指定线程组
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"线程"+i+ "属于"+Thread.currentThread().getThreadGroup().getName()+"线程组");
}
}
}
public class ThreadGroupTest {
public static void main(String [] args){
//获取主线程的线程组
ThreadGroup mainGroup=Thread.currentThread().getThreadGroup();
System.out.println("主线程的组的名字:"+mainGroup.getName());
System.out.println("主线程组是否属于后台线程组:"+mainGroup.isDaemon());
//新建一个线程组名字为“新线程组”,不设置父线程组
ThreadGroup tg=new ThreadGroup("私人");
tg.setDaemon(true);
System.out.println(tg.getName()+"是否是后台线程组:"+tg.isDaemon());
Thread th=new Thread(tg,new TestThread(),"线程1");
System.out.println("1活动的线程有"+tg.activeCount());
th.start();
Thread th1=new Thread(tg,new TestThread(),"线程2");
th1.start();
System.out.println("2活动的线程有"+tg.activeCount());
//Thread t1=new Thread();
}
}
结果:
主线程的组的名字:main
主线程组是否属于后台线程组:false
私人是否是后台线程组:true
1活动的线程有0
2活动的线程有2
线程1线程0属于私人线程组
线程1线程1属于私人线程组
线程2线程0属于私人线程组
线程1线程2属于私人线程组
线程2线程1属于私人线程组
线程1线程3属于私人线程组
线程2线程2属于私人线程组
线程1线程4属于私人线程组
线程2线程3属于私人线程组
线程1线程5属于私人线程组
线程2线程4属于私人线程组
线程1线程6属于私人线程组
线程2线程5属于私人线程组
线程1线程7属于私人线程组
线程2线程6属于私人线程组
线程2线程7属于私人线程组
线程2线程8属于私人线程组
线程2线程9属于私人线程组
线程1线程8属于私人线程组
线程1线程9属于私人线程组

2.线程异常处理

不想在多线程中遇到无谓的Exception,从jdk1.5后,java加强了线程的异常处理,如果线程执行过程中抛出了一个未处理的异常,JVM在结束该线程之前就会自动查找是否有对应的Thread.UncaughtExceptionHandler对象,如果找到该处理对象,将会调用该对象的uncaughtException(Thread t,Throwable e)方法来处理该异常。

自定义线程异常需要继承Thread.UncaughtExceptionHandler

Thread源码:

public interface UncaughtExceptionHandler {  
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p class="artcon">Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}

Thread类中提供两个方法来设置异常处理器:

1.static setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

为该线程类的所有线程实例设置默认的异常处理器

源码:

Public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}

2.setUncaughtExceptionHandler(UncaughtExceptionHandler eh)

为指定线程实例设置异常处理器

源码:

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
checkAccess();
uncaughtExceptionHandler = eh;
}

其实ThreadGroup类继承了Thread.UncaughtExceptionHandler接口,所以每个线程所属的线程组将会作为默认的异常处理器。

所以可以认为当一个线程出现异常时,JVM首先会调用该线程的异常处理器(setUncaughtExceptionHandler),如果找到就执行该异常。没找到就会调用该线程所属线程组的异常处理器。

ThreadGroup类中异常处理器

源码:

public void uncaughtException(Thread t, Throwable e) {  
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}

例子:

/** 
* 定义自己的异常类
*/
class MyEx implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t+"线程出现了异常:"+e);
}
}
/**为主线程设置异常处理器,当程序开始运行时抛出未处理的异常,自定义的异常处理器会起作用*/
class MyThread extends Thread{
public void run(){
int a=5/0;
}
}
public class ExHandler {
public static void main(String []args){
Thread td=new MyThread();
//为指定的td线程实例设置异常处理器
td.setUncaughtExceptionHandler(new MyEx());
td.start();
//设置主线程的异常类
Thread.currentThread().setUncaughtExceptionHandler(new MyEx());
byte [] b=new byte[2];
System.out.println(b[2]);
}
}
结果:
Thread[main,5,main]线程出现了异常:java.lang.ArrayIndexOutOfBoundsException: 2
Thread[Thread-0,5,main]线程出现了异常:java.lang.ArithmeticException: / by zero

七.Callable和Future接口

C#可以把任意方法包装成线程执行体,包括那些有返回值的方法。Java也从jdk1.5开始,加入了Callable接口用来扩展Runnable接口的功能,Callable接口提供一个call()来增强Runnable的run()。因为call()可以有返回值,可以声明抛出异常。

但是Callable是新增的接口 并没有继承Runnable接口,那么肯定不能作为Runnable target来直接作为Thread构造方法的参数。必须由一个中间的类来包装Callable对象。这个类就是实现了Future接口(继承至Runnable接口)的FutureTask类。

CODE:

import java.util.concurrent.Callable;  

public class CallableThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i=0;
for(;i<6;i++){
System.out.println(Thread.currentThread().getName()+"循环:"+i);
}
return i;
}
}
----------------------------------------------------------------------------
public class TestCallable {
public static void main(String []args){
try {
CallableThread ct=new CallableThread();
FutureTask<Integer> target=new FutureTask<Integer>(ct);
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"循环变量:"+i);
if(i==2){
new Thread(target,"子线程").start();
//boolean isDone():如果Callable任务已完成,则返回true,否则返回false
System.out.println(target.isDone());
Thread.sleep(1);
}
}
//V get():返回Callable任务里call()的返回值,调用该方法会导致阻塞,必须等到子线程结束时才会得到返回值
System.out.println("子返回值是:"+target.get());
System.out.println(target.isDone());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
-----------
结果:
main循环变量:0
main循环变量:1
main循环变量:2
false
子线程循环:0
子线程循环:1
子线程循环:2
子线程循环:3
子线程循环:4
main循环变量:3
子线程循环:5
main循环变量:4
子返回值是:6
true

八.线程池

Jdk1.5后java也内置支持线程池,为了提高性能。(当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池)

Jdk1.5提供一个Executors工厂类来产生线程池。工厂类中包含5个静态工厂方法来创建线程池:

一.返回类型是ExecutorService 的共3个:

1. newFixedThreadPool(int nThreads)

创建一个可重用的,具有固定线程数的线程池

CODE:

public static ExecutorService newFixedThreadPool(int nThreads) {  
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

2. ExecutorService newCachedThreadPool()

创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。

CODE:

public static ExecutorService newCachedThreadPool() {  
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

3.newSingleThreadExecutor()

创建一个只有单线程的线程池,等于newFixedThreadPool(1)。

CODE:

public static ExecutorService newSingleThreadExecutor() {  
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

二.返回类型是ScheduledExecutorService(是ExecutorService的子类) 的共2个:

1.newSingleThreadScheduledExecutor()

创建只有一条线程的线程池,它可以在指定延迟后执行线程任务

CODE:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {  
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}

2.newScheduledThreadPool(int corePoolSize)

创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。其中参数指池中所保存的线程数,即使线程时空闲的也被保存在线程池内。

CODE:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {  
return new ScheduledThreadPoolExecutor(corePoolSize);
}

ExecutorService代表尽快执行线程的线程池(只要线程池中有空闲线程立即执行线程任务),程序只需要传入Runnable或Callable对象即可。

ExecutorService提供三个方法来执行线程:

1.Future<?>submit(Runnable target):Runnable中run是没有返回值的所以执行完成后返回null,可以调用Future的isDone(),isCanclled()来判断当前target的执行状态。

2.<T>Future<T>submit(Runnable target,T result):因为Runnable中run是没有返回值,这里显示指定该Runnable对象线程执行完成返回一个值,这个值就是result。

3.<T>Future<T>submit(Callable<T> target):Callable中的call是有返回值的

ScheduledExecutorService提供以下四个方法:

1.ScheduledFuture<V> schedule(Callable(V) c,long delay,TimeUnit unit):指定c任务将在delay延迟后执行。

2.ScheduledFuture<?> shedule(Runnable r,long delay,TimeUnit unit):指定r任务将在delay延迟后执行。

3.ScheduledFuture<?> scheduleAtFixedRate(Runnable r,long initialDelay,long period,TimeUnit unit):指定r任务将在delay延迟后执行,而且以设定的频率重复执行(在initialDelay后开始执行,然后开始依次在initialDelay+period,initialDelay+period*2...处重复执行)。

4.ScheduledFuture<?>scheduledWithFixedDelay(Runnable r,long nitialDelay,long delay,TimeUnit unit):创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行中止和下一次执行开始之间都存在给定的延迟。如果任务的任意依次执行时异常,就会取消后序执行。否则,只能通过程序来显示取消或中止该任务。

当用完一个线程池后,应该调用该线程池的shutdown()方法,启用了shutdown()方法后线程池不再接受新任务,但会将以前所有已提交的任务执行完成,然后启动线程池的关闭序列。

当线程池所有任务都执行完成后,池中所有线程都会死亡,另外也可以调用线程池的shutdownNow()方法来关闭线程池,该方法试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

例子:

class TestThread implements Runnable{  
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadPoolTest {
public static void main(String []args){
//创建一个固定线程数为3的线程池
ExecutorService pool=Executors.newFixedThreadPool(3);
//像线程池提交10个线程
for(int i=0;i<10;i++){
pool.submit(new TestThread());
if(i==4){


}
}
//执行完成后关闭线程
pool.shutdown();
}
}
结果:
pool-1-thread-2:1
pool-1-thread-2:2
pool-1-thread-2:3
pool-1-thread-1:9
pool-1-thread-3:8
pool-1-thread-3:9
pool-1-thread-2:4
......

九.附录:线程相关的类

1.ThreadLocal(线程局部变量)

为了避免并发线程的安全问题,可以添加支持泛型的ThreadLocal类(ThreadLocal<T>)。通过使用ThreadLocal可以简化多线程编程时的并发访问,使用这个工具类可以很简洁的写出有没的多线程程序(例如Hibernate的官方提供HibernateUtil类里设置当前线程的局部变量是当前线程副本中session的值):

Hibernate代码:

public class HibernateUtil {  
/** 日志 */
private static final Log LOG=LogFactory.getLog(HibernateUtil.class);
/** 线程本地变量*/
private static final ThreadLocal MAP = new ThreadLocal();
/** 会话工厂 */
private static final SessionFactory SESSION_FACTORY;
private HibernateUtil() {
}
static {
try {
LOG.debug("HibernateUtil.static - loading config");
SESSION_FACTORY = new Configuration().configure()
.buildSessionFactory();
LOG.debug("HibernateUtil.static - end");
} catch (HibernateException e) {
throw new RuntimeException("建立会话工厂错误" + e.getMessage(), e);
}
}
/**
* 获得一个会话从当前的线程变量,当这个任务完成后,用户必须返回会话关闭方法
*/
public static Session currentSession()throws HibernateException{
Session session=(Session)MAP.get();
//如果会话还没有,就打开会话
if(session==null){
session=SESSION_FACTORY.openSession();
//设置此线程局部变量的值是当前线程副本中session的值
MAP.set(session);
}
return session;
}
/**
* 关闭会话
*/
public static void closeSession(){
Session session=(Session)MAP.get();
MAP.set(null);
if(session!=null){
session.close();
}
}
}

根据上面代码来看:

线程局部变量功能很简单,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。仿佛就好像每一个线程都可以完全拥有该变量

ThreadLocal类提供的常用方法:

1.T get():返回此线程局部变量中当前线程副本中的值。

2.void remove():删除此线程局部变量中当前线程的值。

3.void set(T value):设置此线程局部变量中当前线程副本中的值。

CODE:

/** 
* 线程局部变量测试
* @author Cloudy
*
*/
class Accout{
//定义一个ThreadLocal变量,只要调用该类的线程都会保留该变量的一个副本
private ThreadLocal<String> threadLocal=new ThreadLocal<String>();
//初始化threadLocal
public Accout(String str) {
this.threadLocal.set(str);
System.out.println("-------初始化(为ThreadLocal在main中副本)值是:"+this.threadLocal.get());
}
public String getThreadLocal() {
return threadLocal.get();
}
public void setThreadLocal(String threadLocal) {
this.threadLocal.set(threadLocal);
}
}
/**定义一个线程*/
class MyThread implements Runnable{
//模拟一个Accout
private Accout accout;
public MyThread(Accout accout) {
super();
this.accout = accout;
}
//线程执行体
public void run() {
for(int i=0;i<3;i++){
if(i==2){
//设置此线程局部变量的值为当前线程名字
accout.setThreadLocal(Thread.currentThread().getName());
}
System.out.println(i+"------"+Thread.currentThread().getName()+ "线程局部变量副本值:"+accout.getThreadLocal());
}
}
}
public class ThreadLocalVarTest {
public static void main(String []args){
ExecutorService pool=Executors.newFixedThreadPool(3);
//启动三条线程,公用同一个Accout
Accout ac=new Accout("ThreadLocal本尊");
/*
* 虽然Accout类的只有一个变量所以ThreadLocal类型的变量就导致了同一个Accout对象,
* 当i=2后,将会看到3条线程访问同一个ac 而看到不同的ThreadLocal值。
*/
pool.submit(new MyThread(ac));
pool.submit(new MyThread(ac));
pool.submit(new MyThread(ac));
pool.shutdown();
}
}

结果:

-------初始化(为ThreadLocal在main中副本)值是:ThreadLocal本尊

0------pool-1-thread-1线程局部变量副本值:null

1------pool-1-thread-1线程局部变量副本值:null

2------pool-1-thread-1线程局部变量副本值:pool-1-thread-1

0------pool-1-thread-2线程局部变量副本值:null

1------pool-1-thread-2线程局部变量副本值:null

2------pool-1-thread-2线程局部变量副本值:pool-1-thread-2

0------pool-1-thread-3线程局部变量副本值:null

1------pool-1-thread-3线程局部变量副本值:null

2------pool-1-thread-3线程局部变量副本值:pool-1-thread-3

总结:

ThreadLocal并不能代替同步机制,两者面向的问题领域不同,同步机制是为了多个线程同步对相同资源的并发访问,是多个线程之间进行通信的有效方式。而ThreadLocal是隔离多个线程的数据共享,根本就没有在多个线程之间共享资源,也就更不用对多个线程同步了。

所以:如果进行多个线程之间共享资源,达到线程之间通信功能,就同步。

如果仅仅需要隔离多个线程之间的共享冲突,就是用ThreadLocal。

2.包装线程不安全的集合成为线程安全集合

Java集合中的ArrayList,LinkedList,HashSet,TreeSet,HashMap都是线程不安全的(线程不安全就是当多个线程想这些集合中放入一个元素时,可能会破坏这些集合数据的完整性)

如何将上面的集合类包装成线程安全的呢?

例子:使用Collections的synchronizedMap方法将一个普通HashMap包装成线程安全的类

HashMap hm=Collections.synchronizedMap(new Map());

如果需要包装某个集合成线程安全集合,则应该在创建之后立即包装如上。

3.线程安全的集合类

位于java.util.concurrent包下的ConcurrentHashMap集合和ConcurrentLinkedQueue集合都支持并发访问,分别对应支持并发访问的HashMap和Queue,它们都可以支持多线程并发写访,这些写入线程的所有操作都是线程安全的,但读取的操作不必锁定。(为什么?因为算法 我也看不懂)

当多个线程共享访问一个公共集合时,使用ConcurrentLinkedQueue是一个恰当的选择,因为ConcurrentLinkedQueue不允许使用null元素。ConcurrentLinkedQueue实现了多线程的高效访问,多线程访问ConcurrentLinkedQueue集合时不需要等待。

ConcurrentHashMap支持16条多线程并发写入。

用迭代器访问这两种可支持多线程的集合而言,该迭代器可能不反应出创建迭代器之后所做的修改,但不会抛出异常,而如果Collection作为对象,迭代器创建之后修改,则会抛出ConcurrentModificationException。

   
次浏览       
相关文章

Java微服务新生代之Nacos
深入理解Java中的容器
Java容器详解
Java代码质量检查工具及使用案例
相关文档

Java性能优化
Spring框架
SSM框架简单简绍
从零开始学java编程经典
相关课程

高性能Java编程与系统性能优化
JavaEE架构、 设计模式及性能调优
Java编程基础到应用开发
JAVA虚拟机原理剖析
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

Java 中的中文编码问题
Java基础知识的三十个经典问答
玩转 Java Web 应用开发
使用Spring更好地处理Struts
用Eclipse开发iPhone Web应用
插件系统框架分析
更多...   

Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术

Struts+Spring+Hibernate/EJB+性能优化
华夏基金 ActiveMQ 原理与管理
某民航公司 Java基础编程到应用开发
某风电公司 Java 应用开发平台与迁移
日照港 J2EE应用开发技术框架与实践
某跨国公司 工作流管理JBPM
东方航空公司 高级J2EE及其前沿技术
更多...