java之线程详解

线程

此文参照了http://www.cnblogs.com/riskyer/p/3263032.html的文章。

main方法也是一个线程,它是由虚拟机来启动的,称为主线程。那么我们若是自己创建线程,run()方法就和main方法的作用是一样的,用来存储要运行的内容。

线程中start()和run()方法的区别:

start()是启动线程,并调用run()方法。

而run()仅仅是一个用于存储线程的方法。

线程的状态:创建new Thread()、可运行、运行start()、阻塞wait() 和sleep(time)、消亡stop().

其中可运行是指线程具有执行资格,但是还没有获得cpu的执行权。阻塞时线程会交出执行权,睡眠时间到或者被唤醒后回到可运行状态。

线程中几个常用方法:

1、Thread.yield()方法:

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

2、join()方法

合并线程,Thread的非静态方法join()让一个线程B“加入到另外一个线程A的尾部。在A执行完毕之前,B不能工作。

3、 setDaemon(boolean on)设置成守护线程,当所有非守护线程任务完成后,守护线程无论完成与否都会结束。

线程安全问题解决办法:

1、同步代码块

synchronized(对象){//对象如同锁。机制就类似火车上的卫生间。先判断是否有人,若无人就进去上锁,出来时开锁。

需要被同步的代码;

}

同步的前提:

a.有两个或两个线程以上

b.必须是多个线程使用一个锁

必须保证同步中只有一个线程在执行。线程睡眠时,它所持的任何锁都不会释放。

弊端:多个线程需要判断锁,消耗资源,是程序执行的慢。

2、同步函数

使用synchronized去修饰需要同步的函数就可以了

synchronized只能标记非抽象的方法,不能标识成员变量。

3、静态方法同步

要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)

例如:

public static synchronized int setName(String name){

      Xxx.name = name;

}

等价于
public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

  线程同步小结:

1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。

2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。

3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

4、对于同步,要时刻清醒在哪个对象上同步,这是关键。

5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对原子操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。

6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

volatile关键字

Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。

 

volatile可以用在任何变量前面,但不能用于final变量前面,因为final型的变量是禁止修改的。也不存在线程安全的问题。

 

Java线程:新特征-锁(上)

 

Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.locks包下面,里面有三个重要的接口ConditionLockReadWriteLock

 

 

 

Condition

ConditionObject监视器方法(waitnotify notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 setwait-set)。

Lock

Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。

ReadWriteLock

ReadWriteLock维护了一对相关的锁定,一个用于只读操作,另一个用于写入操作。

 

 

Java线程:新特征-信号量

 

Java的信号量实际上是一个功能完毕的计数器,对控制一定资源的消费与回收有着很重要的意义,信号量常常用于多线程的代码中,并能监控有多少数目的线程等待获取资源,并且通过信号量可以得知可用资源的数目等等,这里总是在强调数目二字,但不能指出来有哪些在等待,哪些资源可用。

private Semaphore sp;    //池相关的信号量

Java线程:新特征-阻塞队列

阻塞队列是Java5线程新特征中的内容,Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素的操作会被阻塞等待,直到有空位为止。同样,当队列为空时候,请求队列元素的操作同样会阻塞等待,直到有可用元素为止。

 

有了这样的功能,就为多线程的排队等候的模型实现开辟了便捷通道,非常有用。

 

java.util.concurrent.BlockingQueue继承了java.util.Queue接口,可以参看API文档。

 

Java线程:新特征-阻塞栈

 

对于阻塞栈,与阻塞队列相似。不同点在于栈是后入先出的结构,每次操作的是栈顶,而队列是先进先出的结构,每次操作的是队列头。

 

 

 

这里要特别说明一点的是,阻塞栈是Java6的新特征。、

 

 

 

Java为阻塞栈定义了接口:java.util.concurrent.BlockingDeque,其实现类也比较多,具体可以查看JavaAPI文档。

 

Java线程:新特征-原子量

 

所谓的原子量即操作变量的操作是原子的,该操作不可再分,因此是线程安全的。

 

 

 

为何要使用原子变量呢,原因是多个线程对单个变量操作也会引起一些问题。在Java5之前,可以通过volatilesynchronized关键字来解决并发访问的安全问题,但这样太麻烦。

 

Java5之后,专门提供了用来进行单变量多线程并发安全访问的工具包java.util.concurrent.atomic,其中的类也很简单。

这里要注意的一点是,原子量虽然可以保证单个变量在某一个操作过程的安全,但无法保证你整个代码块,或者整个程序的安全性。因此,通常还应该使用锁等同步机制来控制整个程序的安全性。

Java线程:新特征-障碍器

Java5中,添加了障碍器类,为了适应一种新的设计需求,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择障碍器了。

java.util.concurrent.CyclicBarrier

 

 ThreadLocal是一个线程级别的局部变量,并非“本地线程”。ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。

下面是线程局部变量(ThreadLocal variables)的关键点:

一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。

ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。

Sleep()、suspend()和wait()之间有什么区别?

Thread.sleep()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了interrupt()方法,它将唤醒那个“睡眠的”线程。

注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep(),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep(),也是当前线程进入睡眠,而不是t线程。t.suspend()是过时的方法,使用suspend()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend()容易引起死锁问题。

object.wait()使当前线程出于“不可运行”状态,和sleep()不同的是wait是object的方法而不是thread。调用object.wait()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调用object.notify(),这样将唤醒原来等待中的线程,然后释放该锁。基本上wait()/notify()与sleep()/interrupt()类似,只是前者需要获取对象锁。

简言之,sleep()是线程类的方法,导致此线程暂停执行指定的时间,给执行机会给其他线程。但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。

wait()是Object类的方法,对此对象调用wait方法,导致此对象放弃对象锁,进入等待此对象锁的等待锁池,只有针对此对象发出notify方法后本线程才进入等待锁定池准备获取对象锁进入运行状态。

当多个线程访问ThreadLocal实例时,每个线程维护ThreadLocal提供的独立的变量副本。

常用的使用可在DAO模式中见到,当DAO类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/jing58/p/6011942.html