——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
Java中对于线程的操作依赖于类Thread,这个类是线程在Java中的抽象
Thread类的构造方法有3个要素和一个可选项,三个参数分别为String name,Runnable target和ThreadGroup group。另外可以指定long stackSize堆栈大小。
可以通过两种方法来建立线程:
1.继承Thread类,覆盖run()方法。
2.实现Runnable接口,实现run()方法,并传入Thread构造函数启动。一般来说,采用这种方式的情况比较多,因为继承的限制比较大。
如果一个Thread子类(比如常见的匿名类)用Runnable对象构造,那么运行取决于其run()是否调用Thread类中的run(),或是target.run();如果不显式的super.run()或者target.run()那么传入的Runnable是无意义的
例子如下:
// duplicate run()
new Thread(new Runnable() {
@Override
public void run() {
sp("anonymous runnable:" + this);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}) {
@Override
public void run() {
while (true) {
sp("anonymous subclass:" + this.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();//anonymous subclass:Thread-
Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。每个 Timer 对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。Timer的任务是由传入的schedule参数TimerTask。而且构造对象即是开始Timer线程。
也可以使用Timer嵌套创建Timer,下面是一个用TimerTask嵌套交替创建Timer执行的例子:
// alternate timer shedule
new Timer().schedule(new YTask(), 2000);
static class YTask extends TimerTask {
int nextSpan;
public YTask(int i) {
this.nextSpan = i == 2 ? 4 : 2;
}
public YTask() {
this(2);
}
@Override
public void run() {
sp("bbbb");
new Timer().schedule(new YTask(nextSpan), 1000 * nextSpan);
}
}
多个线程访问同一个对象或者资源时,如果没有采取同步手段防止竞争很有可能造成非预期的结果,比如这个简单的例子:
// unsynchonize problem
new Thread(new UnsyncedOutput("shengguangzu")).start();
new Thread(new UnsyncedOutput("liuzhijun")).start();
static class UnsyncedOutput implements Runnable {
String content;
public UnsyncedOutput(String content) {
super();
this.content = content;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
output();
}
}
public void output() {
// uncomment to fix
// synchronized (System.out) {
for (char c : content.toCharArray()) {
System.out.print(c);
}
// }
System.out.println();
}
}
在使用同步锁机制时,如果没有仔细分析锁的竞争,在使用多个锁的时候可能造成死锁,即锁无法被正确的释放,相关线程都无法继续,进入阻塞状态。
一个简单的例子:
// dead lock
Object lockA=new Object(),lockB=new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockB) {
sp(Thread.currentThread().getName()+"got lockB");
ts(100);
synchronized (lockA) {//can't got
sp(Thread.currentThread().getName()+"got lockA");//unreachable
}
}
sp(Thread.currentThread().getName()+" finish");//unreachable
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
sp(Thread.currentThread().getName()+"got lockA");
ts(100);
synchronized (lockB) {//can't got
sp(Thread.currentThread().getName()+"got lockB");//unreachable
}
}
sp(Thread.currentThread().getName()+" finish");//unreachable
}
}).start();
依赖线程调度大部分情况可以较好的完成任务,但是在线程较多的时候会比较消耗资源,这时需要一种机制来使线程进入相对长时间的休眠,等到信号才唤醒继续。这就是wait和notify机制,下面是一个利用wait和notify机制交替执行线程的例子:
// wait & notify
Business biz=new Business();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
biz.doA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
biz.doB();
}
}).start();
static class Business{
volatile int signal;
public Business() {
super();
this.signal=1;
}
public void doA(){
while(true){
synchronized (this) {
while(signal!=1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sp("start A in "+Thread.currentThread().getName());
ts(new Random().nextInt(2000));
sp("done A in "+Thread.currentThread().getName());
this.signal=2;
this.notify();
}
}
}
public void doB(){
while(true){
synchronized (this) {
while(signal!=2){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sp("start B in "+Thread.currentThread().getName());
ts(new Random().nextInt(2000));
sp("done B in "+Thread.currentThread().getName());
this.signal=1;
this.notify();
}
}
}
}
由于多个线程可能竞争使用一个单例,如果采用懒汉式,则有可能产生超过一个实例对象,这时就需要双重检查,即进入同步块后再检查一次。
下面是一些单例的实现模式,均为线程安全:
// double check singleton
static class SyncSingleton {
//volatile to avoid exception
private static volatile SyncSingleton instance;
private SyncSingleton() {
};
public static SyncSingleton getInstance() {
if (instance == null) {// check before lock
synchronized (SyncSingleton.class) {
if (instance == null) {// check after lock
instance = new SyncSingleton();
}
}
}
return instance;
}
}
// another double check way
// with prevent deserialize in Serializable
static class SyncSingleton2 implements Serializable{
/**
*
*/
private static final long serialVersionUID = 4675690059690737395L;
//volatile to avoid exception
private volatile static SyncSingleton instance;
private SyncSingleton2() {
};
//this way has an lower efficient
public static synchronized SyncSingleton getInstance() {
if (instance == null) {// check after lock
instance = new SyncSingleton();
}
return instance;
}
//this method to prevent deserialize crack
private Object readResolve() throws ObjectStreamException{
return instance;
}
}
//hungry way
static class HungrySingleton{
private static HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){};
public static HungrySingleton getInstance(){
return instance;
}
}
// inner class way it's lazy
static class InnerClassSingleton{
private InnerClassSingleton(){};
private static class SingletonHolder{
private static InnerClassSingleton instance=new InnerClassSingleton();
}
public InnerClassSingleton getInstance(){
return SingletonHolder.instance;
}
}
// singleton by enum
// concurrent safe ,deserialize safe,and easy
static enum EnumSingleton {
INSTANCE;
private EnumSingleton() {
};// that's all we need
}
有时我们需要在线程级别共享一个数据,一般的,可以使用一个静态HashMap,用线程做key,在使用处调用即可,也不需要同步锁。
但是Java已经提供了一个使用更方便的容器:
ThreadLocal
这个容器很简单,只有4个方法, T get() ,protected T initialValue() , void remove() , void set(T value) 。一目了然,在操作容器内对象的时候,ThreadLocal会自动查询当前线程上下文,对对应的对象进行操作。
简单的示例:
// ThreadLocal demo
final ThreadLocal ti = new ThreadLocal();
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int val = r.nextInt(10000);
ti.set(val);
sp("ThreadLocal val of " + Thread.currentThread().getName()
+ " set to " + val);
ts(r.nextInt(3000));
sp("ThreadLocal val of " + Thread.currentThread().getName()
+ " is " + ti.get());
}
}).start();
}
类的小工具包,支持在单个变量上解除锁的线程安全编程。
一般来说,实现了在一个元素上的一次复合操作的原子性,即保证其修改符合预期。
已经支持的类型有Integer,Long,Boolean,还有Reference的引用类型。
Executor 定义了一个提交Runnable任务的执行器。而ExecutorService则定义了更为实用的可以提交Runnable任务或者Callable任务,并且可以关闭的执行器。
Java已经通过线程池实现了ExecutorService,而且提供了Executors的工厂方法来产生各种线程池执行器。
建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。默认的存活时间是60秒。
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在某个线程被显式地关闭之前,池中的线程将一直存在。
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务。
泛型返回的ExecutorService任务,其中Callable指定一个泛型返回类型,交给指定的ExecutorService执行之后,返回Future,在异步执行过程中,可以通过查询Future的isDone() 来了解完成状况。完成之后,可以通过Future的get() 来获取结果,如果get() 时尚未完成,则将阻塞。
Java中对此的默认实现是FutureTask
// ThreadPool demo with Future
ExecutorService exes = Executors.newFixedThreadPool(3);
final int times = 20;
final List> results = new ArrayList>(
times);
for (int i = 0; i < times; i++) {
results.add(exes.submit(() -> {
ts(1000);
int nextInt = r.nextInt(1000);
sp("gen num :" + nextInt + " in "
+ Thread.currentThread().getName());
return nextInt;
}));
}
for (Future future : results) {
try {
sp(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
exes.shutdown();
Java1.5为锁和等待条件提供一个接口和类的框架,它相对内置的synchonized和wait/notify机制有所增强。
synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
而Lock提供了更为灵活的机制:它可以以任意顺序解锁,例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 还实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。
一般的使用Lock的格式:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
ReadWriteLock实现了对同一资源的一对相关锁,其特殊之处在于没有写锁的情况下允许并发读锁,在读的发生频率较高的时候可以提高系统效率。而写锁依然是独占的。
一个简单的例子:读操作可以并发
//ReadWriteLock
ReadWriteLockDemo lockObj=new ReadWriteLockDemo();
for (int i = 0; i < 4; i++) {
if(i==3){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
lockObj.set(r.nextInt(1000));
}
}
}).start();
}
new Thread(new Runnable() {
@Override
public void run() {
while(true){
lockObj.get();
}
}
}).start();
}
static class ReadWriteLockDemo{
Object data;
ReadWriteLock rwl=new ReentrantReadWriteLock();
{
data=123;
}
public void get(){
rwl.readLock().lock();
try {
sp("prepare to read in"+Thread.currentThread().getName());
ts(new Random().nextInt(300));
sp("data is : "+data+" from "+Thread.currentThread().getName());
ts(new Random().nextInt(100));
sp("done to read in"+Thread.currentThread().getName());
}finally{
rwl.readLock().unlock();
}
ts(new Random().nextInt(1000));
}
public void set(Object o){
rwl.writeLock().lock();
try {
sp("prepare to write in"+Thread.currentThread().getName());
ts(new Random().nextInt(300));
data=o;
sp("data has set to : "+data+" from "+Thread.currentThread().getName());
ts(new Random().nextInt(100));
sp("done to write in"+Thread.currentThread().getName());
}finally{
rwl.writeLock().unlock();
}
ts(new Random().nextInt(1000));
}
}
Condition 实现了一个面向锁的线程控制等待和唤醒的条件机制,相较于wait¬ify机制,它能对同一个锁添加多个Condition,以实现多重状态的判断。
这是一个利用Condition实现3个操作轮流执行的例子:
// Condition
final ConditionDemo cd=new ConditionDemo();
new Thread(() -> cd.BusinessA()).start();
new Thread(() -> cd.BusinessB()).start();
new Thread(() -> cd.BusinessC()).start();
new Thread(() -> cd.BusinessA()).start();
new Thread(() -> cd.BusinessB()).start();
new Thread(() -> cd.BusinessC()).start();
static class ConditionDemo {
volatile int signal = 1;
Lock cl = new ReentrantLock();
Condition c1 = cl.newCondition(), c2 = cl.newCondition(), c3 = cl.newCondition();
public void BusinessA() {
while (true) {
cl.lock();
try {
while (signal != 1) {
try {
c1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sp("BusinessA in "+Thread.currentThread().getName());
ts(1000);
this.signal=2;
c2.signal();
} finally {
cl.unlock();
}
}
}
public void BusinessB() {
while (true) {
cl.lock();
try {
while (signal != 2) {
try {
c2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sp("BusinessB in "+Thread.currentThread().getName());
ts(1000);
this.signal=3;
c3.signal();
} finally {
cl.unlock();
}
}
}
public void BusinessC() {
while (true) {
cl.lock();
try {
while (signal != 3) {
try {
c3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sp("BusinessC in "+Thread.currentThread().getName());
ts(1000);
this.signal=1;
c1.signal();
} finally {
cl.unlock();
}
}
}
}
Semaphore提供了一个基于许可量的线程调度,它可以实现对有限资源的线程分配。每个线程可以申请(一个或多个)许可量,当当前许可量不足时,线程会阻塞;线程完成资源使用后,应该释放许可量,一般来说,应该释放与申请相等的许可量。注意,许可量不仅可以由线程申请和释放,也可以由外部管理,这样就提供了相当的弹性。
Semaphore的构造方法接收一个int作为许可量。当设置为1的时候,就实现了一个互斥锁。
Semaphore的构造方法允许传入一个标志以实现“公平”的分配,即先进先出FIFO的阻塞队列,但tryAcquire方法不实现此特性。
CyclicBarrier一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
CountDownLatch 实现了一个倒计锁,当倒计归零之前,所有被CountDownLatch所await的线程将阻塞。线程或者外部都可以调用CountDownLatch的countDown() 方法来减少计数。
当CountDownLatch的构造方法给定参数1时,就实现了一个命令式的线程控制开关,而且可以实现一对多管理。
可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。
多种阻塞队列,包括单向和双向的,线程同步,他们也可以像Java Collectyions Framework中的其他成员一样进行基本的操作,但通常不会有效执行。但主要的增强在于引入了阻塞性方法put & take ,当无法完成操作时(例如放入满队列,从空队列取)会阻塞当前线程直至队列发生变化以完成操作。
Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。其延时依赖于元素的Delayed实现。
一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。也就是说,队列直接阻塞put和take的线程直到配对而传递。
使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。
优先级队列的元素按照顺序进行排序,并且提供了阻塞获取操作。不保证具有同等优先级的元素的顺序。
一些变体容器,其中所有可变操作(add、set 等等)都是通过对底层进行一次新的复制来实现的。这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更有效。