Java多线程编程

 

2013-4-17号补充:

秒杀多线程面试题系列

    该系列是我参加微软亚洲研究院,腾讯研究院,迅雷面试时所整理的。系列先示范如何使用多线程,再详细分析多线程的重点难点必考点——多线程同步互斥问题。各文章讲解生动细致,针对性强。必定也能助你在面试中秒杀所有多线程面试题。

http://blog.csdn.net/column/details/killthreadseries.html

 

多线程编程是非常有必要学习的,在android的push Service中,也会用到thread,所以这篇文章,绝对有必要看一下。

1.其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。

2.但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

3.在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。

 注意:不要直接使用继承Thread类的方法来定义线程,这种方法已不被推荐了。应该从运行机制上减少需要并行运行的任务数量。

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

4. 不要调用Thread类或者Runnable对象的run()方法。直接调用run方法,只会执行同一个线程任务,而不会启动新的线程。应该调用Thread.start方法

5. interrupted()和isInterrupted()方法的区别:前者是静态后者是实例方法,前者调用后会清除interrupt状态后者不会。连着都用来检测interrupt状态

6.守护线程应该永远不去访问固有资源,因为他在任何时候都有可能被中断。

7. 线程的run方法不能抛出任何被检测的异常,这种异常会导致线程终止。

   但是不需要任何catch子句来处理可以被传播的异常,在死亡之前这种异常被传递到一个用于捕获异常的处理器。该处理器实现Thread.UncaufhtExceptionHandler接口打类,这个接口只有一个方法void uncaughtException(Thread t, Throwable e)

8. threadObj.interrupt 方法并不直接中断线程或者抛出InterruptedException,而是设置 interrupted 标志位。Object.wait, Thread.sleep方法,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常。


Thread.interrupted = currentThread.isInterrupted(true) , 会返回当前的interrupted状态位,并清空状态

thread.isInterrupted = isInterrupted(false) 只返回状态,不做清空处理


Object.wait, Thread.join, Thread.sleep, interrupt调用后,会清空其interrupt状态,并抛出InterruptedException异常

9.除以上清空外,其它阻塞,例如:IO操作的阻塞,java.nio.channels.InterruptibleChannel, channels.Selector, interrupt调用后,会设置其interrupt状态


假如在IO阻塞的interrupt调用后,不进行Thread.interrupted调用清除标志位,则下一次IO调用,会直接抛出异常。


nio.AbstractInterruptibleChannel通过实现Interruptible接口的对象,监听线程的interrupt事件,通过 sun.misc.SharedSecrets.getJavaLangAccess().blockedOn(Thread.currentThread(), interruptibleObject); 注册监听器

在Channel每次做动作时,会通过Thread.currentThread().isInterrupted()检查线程的当前状态,假如正处于interrupted状态中,则调用interruptibleObject.interrupt方法

上面标红色的第九条没看懂。

10.Java 之前有个api函数可以直接关闭线程, stop(), 后来, 取消了. 其替代的方式主要有两种:
1)自己加入一个成员变量, 我们在程序的循环里面, 轮流的去检查这个变量, 变量变化时,就会退出这个线程. 代码示例如下
package com.test;
public class StopThread extends Thread {
private boolean _run = true;
public void stopThread(boolean run) {
this._run = !run;
}

@Override
public void run() {
while(_run) {
///
//
数据处理
///
}
//super.run();
}
public static void main(String[] args) {
StopThread thread = new StopThread();
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//
停止线程
thread.stopThread(true);
}

}
2.)方法1 虽然可以可以处理好, 不过, 在有阻塞线程的语句的时候往往不能处理好. 比如, 设计到Socket的阻塞语句. 虽然java有提供异步io但是异步io是在程序里不断去查询有没有消息的, 所以耗电量可想而知, 对手机这种设备来说往往不适用.
那么阻塞的语句,怎么终止线程呢?
Java虽然deprecate了一个stop,但是,提供了interrupt(),这个方法是安全的. 这个中断方法可以将阻塞的线程唤醒过来, 但是注意 他不能将非阻塞的线程中断. 中断的同时,会抛出一个异常InterruptedException. 幸运的是, SocketChannel.connect() .read() 阻塞方法都会接受中断,ClosedByInterruptException.
这时我们不轮询变量了, 轮询当前线程是否被中断, 代码

package com.test;
public class StopThread extends Thread {
@Override
public void run() {
try {
System.out.println("start");
while(!this.isInterrupted()) {
///
//
数据处理
///


}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("stop");
//super.run();
}
public static void main(String[] args) {
StopThread thread = new StopThread();
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

thread.interrupt();
System.out.println("interrupt");
}


}

 

http://www.blogjava.net/jinfeng_wang/archive/2008/04/27/196477.html

 http://silentlakeside.iteye.com/blog/1183199

http://programming.iteye.com/blog/158568

 

Java程序员面试中的多线程问题

http://www.csdn.net/article/2012-05-28/2806046

 

notify & notifyAll

http://xu20cn.blog.51cto.com/274020/128777

 

notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:

notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
notify则文明得多他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。

下面是一个很好的例子:

import java.util.*;

class Widget...{}
class WidgetMaker extends Thread...{
    List<Widget> finishedWidgets=new ArrayList<Widget>();
    public void run()...{
        try...{
            while(true)...{
                Thread.sleep(5000);//act busy
                Widget w=new Widget();
                //也就是说需要5秒钟才能新产生一个Widget,这决定了一定要用notify而不是notifyAll
                //因为上面两行代码不是同步的,如果用notifyAll则所有线程都企图冲出wait状态
                //第一个线程得到了锁,并取走了Widget(这个过程的时间小于5秒,新的Widget还没有生成)
                //并且解开了锁,然后第二个线程获得锁(因为用了notifyAll其他线程不再等待notify语句
                //,而是等待finishedWidgets上的锁,一旦锁放开了,他们就会竞争运行),运行
                //finishedWidgets.remove(0),但是由于finishedWidgets现在还是空的,
                //于是产生异常
                //***********这就是为什么下面的那一句不能用notifyAll而是要用notify
                                
                synchronized(finishedWidgets)...{
                    finishedWidgets.add(w);
                    finishedWidgets.notify(); //这里只能是notify而不能是notifyAll
                }
            }
        }
        catch(InterruptedException e)...{}
    }
    
    public Widget waitForWidget()...{
        synchronized(finishedWidgets)...{
            if(finishedWidgets.size()==0)...{
                try...{
                    finishedWidgets.wait();
                }
                catch(InterruptedException e)
                ...{}
            }
            return finishedWidgets.remove(0);
        }
    }
}
public class WidgetUser extends Thread...{
    private WidgetMaker maker;
    public WidgetUser(String name,WidgetMaker maker)...{
        super(name);
        this.maker=maker;
    }
    public void run()...{
        Widget w=maker.waitForWidget();
        System.out.println(getName()+"got a widget");
    }
   

    public static void main(String[] args) ...{
        WidgetMaker maker=new WidgetMaker();
        maker.start();
        new WidgetUser("Lenny",maker).start();
        new WidgetUser("Moe",maker).start();
        new WidgetUser("Curly",maker).start();

    }

}

 

 

 

2013-4-12补充:

Java编程保证线程安全有哪三种方法


Java编程保证线程安全的三种方法  1,保证线程安全的三种方法:a,不要跨线程访问共享变量b,使共享变量是final类型的c,将共享变量的操作加上同步。  2,一开始就将类设计成线程...
       Java编程保证线程安全的三种方法

   1,保证线程安全的三种方法:a,不要跨线程访问共享变量b,使共享变量是final类型的c,将共享变量的操作加上同步。

   2,一开始就将类设计成线程安全的,比在后期重新修复它,更容易。

   3,编写多线程程序,首先保证它是正确的,其次再考虑性能。

   4,无状态或只读对象永远是线程安全的。

   5,不要将一个共享变量裸露在多线程环境下(无同步或不可变性保护)

   6,多线程环境下的延迟加载需要同步的保护,因为延迟加载会造成对象重复实例化

   7,对于volatile声明的数值类型变量进行运算,往往是不安全的(volatile只能保证可见性,不能保证原子性)。详见volatile原理与技巧中,脏数据问题讨论。

   8,当一个线程请求获得它自己占有的锁时(同一把锁的嵌套使用),我们称该锁为可重入锁。在jdk1.5并发包中,提供了可重入锁的java实现-ReentrantLock。

   9,每个共享变量,都应该由一个唯一确定的锁保护。创建与变量相同数目的ReentrantLock,使他们负责每个变量的线程安全。

   10,虽然缩小同步块的范围,可以提升系统性能。但在保证原子性的情况下,不可将原子操作分解成多个synchronized块。

   11,在没有同步的情况下,编译器与处理器运行时的指令执行顺序可能完全出乎意料。原因是,编译器或处理器为了优化自身执行效率,而对指令进行了的重排序(reordering)。

   12,当一个线程在没有同步的情况下读取变量,它可能会得到一个过期值,但是至少它可以看到那个线程在当时设定的一个真实数值。而不是凭空而来的值。这种安全保证,称之为最低限的安全性(out-of-thin-air safety)

   在开发并发应用程序时,有时为了大幅度提高系统的吞吐量与性能,会采用这种无保障的做法。但是针对,数值的运算,仍旧是被否决的。

   13,volatile变量,只能保证可见性,无法保证原子性。

   14,某些耗时较长的网络操作或IO,确保执行时,不要占有锁。

   15,发布(publish)对象,指的是使它能够被当前范围之外的代码所使用。(引用传递)对象逸出(escape),指的是一个对象在尚未准备好时将它发布。

   原则:为防止逸出,对象必须要被完全构造完后,才可以被发布(最好的解决方式是采用同步)

   this关键字引用对象逸出

   例子:在构造函数中,开启线程,并将自身对象this传入线程,造成引用传递。而此时,构造函数尚未执行完,就会发生对象逸出了。

   16,必要时,使用ThreadLocal变量确保线程封闭性(封闭线程往往是比较安全的,但一定程度上会造成性能损耗)封闭对象的例子在实际使用过程中,比较常见,例如hibernate openSessionInView机制,jdbc的connection机制。

  17,单一不可变对象往往是线程安全的(复杂不可变对象需要保证其内部成员变量也是不可变的)良好的多线程编程习惯是:将所有的域都声明为final,除非它们是可变的

   18,保证共享变量的发布是安全的a,通过静态初始化器初始化对象(jls 12.4.2叙述,jvm会保证静态初始化变量是同步的)b,将对象申明为volatile或使用AtomicReference c,保证对象是不可变的d,将引用或可变操作都由锁来保护

   19,设计线程安全的类,应该包括的基本要素:a,确定哪些是可变共享变量b,确定哪些是不可变的变量c,指定一个管理并发访问对象状态的策略

   20,将数据封装在对象内部,并保证对数据的访问是原子的。建议采用volatile javabean模型或者构造同步的getter,setter。

   21,线程限制性使构造线程安全的类变得更容易,因为类的状态被限制后,分析它的线程安全性时,就不必检查完整的程序。

   22,编写并发程序,需要更全的注释,更完整的文档说明。

   23,在需要细分锁的分配时,使用java监视器模式好于使用自身对象的监视器锁。前者的灵活性更好。

   Object target = new Object();

   // 这里使用外部对象来作为监视器,而非this

   synchronized(target) {

   // TODO

   }

    针对java monitor pattern,实际上ReentrantLock的实现更易于并发编程。功能上,也更强大。

   24,设计并发程序时,在保证伸缩性与性能折中的前提下,优先考虑将共享变量委托给线程安全的类。由它来控制全局的并发访问。

   25,使用普通同步容器(Vector,Hashtable)的迭代器,需要外部锁来保证其原子性。原因是,普通同步容器产生的迭代器是非线程安全的。

   26,在并发编程中,需要容器支持的时候,优先考虑使用jdk并发容器(ConcurrentHashMap,ConcurrentLinkedQueue,CopyOnWriteArrayList。。。)。

   27,ConcurrentHashMap,CopyOnWriteArrayList并发容器的迭代器,以及全范围的size(),isEmpty()都表现出弱一致性。他们只能标示容器当时的一个数据状态。无法完整响应容器之后的变化和修改。

   28,使用有界队列,在队列充满或为空时,阻塞所有的读与写操作。(实现生产-消费的良好方案)BlockQueue下的实现有LinkedBlockingQueue与ArrayBlockingQueue,前者为链表,可变操作频繁优先考虑,后者为数组,读取操作频繁优先考虑。PriorityBlockingQueue是一个按优先级顺序排列的阻塞队列,它可以对所有置入的元素进行排序(实现Comparator接口)

   29,当一个方法,能抛出InterruptedException,则意味着,这个方法是一个可阻塞的方法,如果它被中断,将提前结束阻塞状态。当你调用一个阻塞方法,也就意味着,本身也称为了一个阻塞方法,因为你必须等待阻塞方法返回。

   如果阻塞方法抛出了中断异常,我们需要做的是,将其往上层抛,除非当前已经是需要捕获异常的层次。如果当前方法,不能抛出InterruptedException,可以使用Thread。currentThread。interrupt()方法,手动进行中断。

 


总结的挺好
http://hllvm.group.iteye.com/group/wiki/2877-synchronized-volatile
http://blog.sina.com.cn/s/blog_5f54f0be0100vxb8.html

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值