一、简介
描述
进程(process):是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。
线程(thread):进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。可以看成是轻量级的进程,是CPU调度和分派的基本单位。
区别
1、调度 :从上面的定义可以看出一个是调度和分派的基本单位,一个是拥有资源的基本单位
2、共享地址空间,资源:进程拥有各自独立的地址空间,资源,所以共享复杂,需要用IPC,同步简单; 线程共享所属进程的资源,共享简单,但同步复杂,要通过加锁等措施。
3、占用内存,cpu: 进程占用内存多,切换复杂,CPU利用率低; 线程占用内存少,切换简单,CPU利用率高。
4、相互影响: 进程间不会相互影响; 一个线程挂掉会导致整个进程挂掉。
二、线程的生命周期及五种基本状态
关于Java中线程的生命周期图
Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
三、Java多线程的创建及启动
Java中线程的创建常见有如三种基本形式
1.继承Thread类,重写该类的run()方法。
class MyThread extends Thread {
private int i = 0;
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread myThread1 = new MyThread(); // 创建一个新的线程 myThread1 此线程进入新建状态
Thread myThread2 = new MyThread(); // 创建一个新的线程 myThread2 此线程进入新建状态
myThread1.start(); // 调用start()方法使得线程进入就绪状态
myThread2.start(); // 调用start()方法使得线程进入就绪状态
}
}
}
}
如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。
2.实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
class MyRunnable implements Runnable {
private int i = 0;
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
相信以上两种创建新线程的方式大家都很熟悉了,那么Thread和Runnable之间到底是什么关系呢?我们首先来看一下下面这个例子。
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Runnable myRunnable = new MyRunnable();
Thread thread = new MyThread(myRunnable);
thread.start();
}
}
}
}
class MyRunnable implements Runnable {
private int i = 0;
public void run() {
System.out.println("in MyRunnable run");
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class MyThread extends Thread {
private int i = 0;
public MyThread(Runnable runnable){
super(runnable);
}
public void run() {
System.out.println("in MyThread run");
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
同样的,与实现Runnable接口创建线程方式相似,不同的地方在于
Thread thread = new MyThread(myRunnable);
那么这种方式可以顺利创建出一个新的线程么?答案是肯定的。至于此时的线程执行体到底是MyRunnable接口中的run()方法还是MyThread类中的run()方法呢?通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在Runnable接口中定义的方法。
public interface Runnable { public abstract void run();}
我们看一下Thread类中对Runnable接口中run()方法的实现:
public void run() { if (target != null) { target.run(); }}
也就是说,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。但是上述给到的列子中,由于多态的存在,根本就没有执行到Thread类中的run()方法,而是直接先执行了运行时类型即MyThread类中的run()方法。
----------------------------------------------------------------
实现Runnable接口相比继承Thread类有如下好处:
- 避免点继承的局限,一个类可以继承多个接口。
- 适合于资源的共享
-----------------------------------------------------------------
3.使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
看着好像有点复杂,直接来看一个例子就清晰了。
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 与run()方法不同的是,call()方法具有返回值
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。那么看下FutureTask类的定义:
public class FutureTask<V> implements RunnableFuture<V> { //....}
public interface RunnableFuture<V> extends Runnable, Future<V> { void run();}
于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。
执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢?
原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。
上述主要讲解了三种常见的线程创建方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:
不能对同一线程对象两次调用start()方法。
四、 Java多线程的就绪、运行和死亡状态
就绪状态转换为运行状态:当此线程得到处理器资源;
运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。
运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。
此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。
由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:
public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
thread.start();
}
if(i == 40){
myRunnable.stopThread();
}
}
}
}
class MyRunnable implements Runnable {
private boolean stop;
public void run() {
for (int i = 0; i < 100 && !stop; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public void stopThread() {
this.stop = true;
}
}
五、线程的优先级和让步
线程的让步是通过Thread.yield()来实现,暂停当前正在执行的线程对象,并执行其他线程。线程存在优先级,优先级范围在1~10,JVM线程调度程序是基于优先级的抢先调度机制。
设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级,可以通过setPriority(int newPriority)更改优先级。
六、线程的同步与锁
线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。
关于锁和同步,有以下几个要点:
1、只能同步方法,而不能同步变量和类;
2、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6、线程睡眠时,它所持的任何锁都不会释放。
7、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
10、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
11、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
七、线程的交互
void notify():唤醒在此对象监视器上等待的单个线程。
void notifyAll():唤醒在此对象监视器上等待的所有线程。
void wait():导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法。
wait()还有另外两个重载方法:
void wait(long timeout):导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过制定的时间量。
void wait(long timeout, int nanos):导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
以下为生产者-消费者-仓库模型:
public class Test {
public static void main(String[] args) {
Godown godown = new Godown(30);
Consumer c1 = new Consumer(50, godown);
Consumer c2 = new Consumer(20, godown);
Consumer c3 = new Consumer(10, godown);
Producer p1 = new Producer(10, godown);
Producer p2 = new Producer(10, godown);
Producer p3 = new Producer(10, godown);
Producer p4 = new Producer(10, godown);
Producer p5 = new Producer(10, godown);
Producer p6 = new Producer(10, godown);
Producer p7 = new Producer(80, godown);
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}
/**
* @author dong
*
*/
class Godown {
public static final int MAX_SIZE = 100;// 最大库存量
public int curnum;// 当前库存量
Godown() {
}
Godown(int curnum) {
this.curnum = curnum;
}
/**
* 生产指定数量的产品
*
* @param neednum
*/
public synchronized void produce(int neednum) {
// 测试是否需要产品
while (neednum + curnum > MAX_SIZE) {
System.out.println("要生产的产品数量为:" + neednum + "超过剩余库存量:" + (MAX_SIZE - curnum) + ",暂时不能执行生产任务!");
try {
// 当前的生产线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 满足生产条件,则进行生产,这里简单的更改当前库存量
curnum += neednum;
System.out.println("已经生产了" + neednum + "个产品,现库存量为:" + curnum);
// 唤醒在此对象监视器上的等待的所有线程
notifyAll();
}
/**
* 消费制定数量的产品
*
* @param neednum
*/
public synchronized void consume(int neednum) {
// 测试是否可以消费
while (curnum < neednum) {
try {
// 当前的生产线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 满足消费条件,则进行消费,这里简单的更改当前库存量
curnum -= neednum;
System.out.println("已经消费了:" + neednum + "个产品,现仓库储量为:" + curnum);
// 唤醒再次对象监视器上等待的所有线程
notifyAll();
}
}
/**
* 生产者
*
* @author dong
*
*/
class Producer extends Thread {
private int neednum;// 生产产品的数量
private Godown godown;// 仓库
Producer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
public void run() {
// 生产指定数量的产品
godown.produce(neednum);
}
}
/**
* 消费者
*
* @author dong
*/
class Consumer extends Thread {
private int neednum;//生产产品的数量
private Godown godown;//仓库
Consumer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
public void run() {
//消费制定数量的产品
godown.consume(neednum);
}
}
八、线程的调度-合并
join为非静态方法,定义如下:
void join() //等待该线程终止。
void join(long millis) //等待该线程终止的时间最长为 millis毫秒。
void join(long millis,int nanos) //等待该线程终止的时间最长为 millis毫秒 + nanos 纳秒。
九、线程的调度-守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
setDaemon方法的详细说明:public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 如果该线程处于活动状态。
SecurityException - 如果当前线程无法修改该线程。
另请参见:
isDaemon(), checkAccess()
十、volatile关键字
1、volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。但是用volatile修饰之后就变得不一样了:
第一:使用volatile关键字会强制将修改的值立即写入主存;
第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效;
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。那么线程1读取到的就是最新的正确的值。
十一、线程池
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
1、固定大小的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @param :Java线程:线程池
* @author dong 2017-07-10
*/
public class MyThread extends Thread{
public void run(){
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
}
}
class Test{
public static void main(String[] args) {
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//创建实现了Runnable接口对象,Thread对象当然也实现可Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
运行结果
Connected to the target VM, address: '127.0.0.1:14808', transport: 'socket'
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-2正在执行。。。
Disconnected from the target VM, address: '127.0.0.1:14808', transport: 'socket'
Process finished with exit code 0
2、单任务线程池
修改上述实例中的
ExecutorService pool = Executors.newFixedThreadPool(2);
为
//创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程
ExecutorService pool = Executors.newSingleThreadExecutor();
运行结果
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
Process finished with exit code 0
3、可变尺寸的线程池
修改实例1中的
ExecutorService pool = Executors.newFixedThreadPool(2);
为
//创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
ExecutorService pool = Executors.newCachedThreadPool();
运行结果
pool-1-thread-1正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-3正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-5正在执行。。。
Process finished with exit code 0
创建自定义线程池的构造方法很多,本例中参数的含义如下:
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
用给定的初始参数和默认的线程工厂及处理程序创建新的ThreadPoolExecutor。使用Executors工厂方法之一
比使用此通用构造方法方便得多。
参数:
corePoolSize -池中所保存的线程数,包括空闲线程。
maximumPoolSize -池中允许的最大线程数。
keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime参数的时间单位。
workQueue -执行前用于保持任务的队列。此队列仅保持由execute方法提交的Runnable任务。
抛出:
IllegalArgumentException -如果 corePoolSize或 keepAliveTime小于零,或者 maximumPoolSize小于或等
于零,或者 corePoolSize大于 maximumPoolSize。
NullPointerException -如果workQueue为 null
十二、锁
以下为死锁:
public class Test1 {
public static void main(String[] args) {
DeadlockRisk1 dead = new DeadlockRisk1();
MyThread t1 = new MyThread(dead, 1, 2);
MyThread t2 = new MyThread(dead, 3, 4);
MyThread t3 = new MyThread(dead, 5, 6);
MyThread t4 = new MyThread(dead, 7, 8);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyThread extends Thread {
private DeadlockRisk1 dead;
private int a,b;
MyThread(DeadlockRisk1 dead, int a, int b) {
this.dead = dead;
this.a = a;
this.b = b;
}
@Override
public void run() {
dead.read();
dead.write(a, b);
}
}
class DeadlockRisk1 {
private static class Resource {
public int value;
}
private Resource resourceA = new Resource();
private Resource resourceB = new Resource();
public int read() {
synchronized (resourceA) {
System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceA的锁!");
synchronized (resourceB) {
System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceB的锁!");
return resourceB.value + resourceA.value;
}
}
}
public void write(int a, int b) {
synchronized (resourceA) {
System.out.println("write():" + Thread.currentThread().getName() + "获取到了resourceA的锁!");
synchronized (resourceB) {
System.out.println("write():" + Thread.currentThread().getName() + "获取到了resourceB的锁!");
resourceA.value = a;
resourceB.value = b;
}
}
}
}
1、普通锁
利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.locks包下面,里面有三个重要的接口Condition、Lock、ReadWriteLock。
Condition
|
Condition将Object监视器方法(wait、notify和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set)。
|
Lock
|
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
|
ReadWriteLock
|
ReadWriteLock维护了一对相关的锁定,一个用于只读操作,另一个用于写入操作。
|
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author dong
* @function Java线程:锁
*/
public class Test {
public static void main(String[] args) {
//创建并发访问的账户
MyCount myCount = new MyCount("11111111111111", 1000);
//创建一个锁对象
Lock lock = new ReentrantLock();
//创建一个线程池
ExecutorService pool = Executors.newCachedThreadPool();
//创建一些并发访问用户,一个信用卡,存的存,取的取
User u1 = new User("A", myCount, -400, lock);
User u2 = new User("B", myCount, 400, lock);
User u3 = new User("C", myCount, -600, lock);
User u4 = new User("D", myCount, 100, lock);
//在线程池中执行各个用户的操作
pool.execute(u1);
pool.execute(u2);
pool.execute(u3);
pool.execute(u4);
//关闭线程池
pool.shutdown();
}
}
/**
* 信用卡的用户
*/
class User implements Runnable {
private String name; //用户名
private MyCount myCount; //索要操作的账户
private int iocash; //操作的金额,当然有正负之分了
private Lock myLock; //执行操作所需要的锁对象
User (String name, MyCount myCount, int iocash, Lock myLock) {
this.name = name;
this.myCount = myCount;
this.iocash = iocash;
this.myLock = myLock;
}
public void run() {
//获取锁
myLock.lock();
//执行现金业务
System.out.println(name + "正在操作" + myCount + "账户,金额为" +
iocash + ",当前金额为" + myCount.getCash());
myCount.setCash(myCount.getCash() + iocash);
System.out.println(name + "操作" + myCount + "账户成功,金额为" +
iocash + ",当前金额为" + myCount.getCash());
//释放锁,否则别的线程就没有机会了
myLock.unlock();
}
}
/**
*信用卡账户,可随意透支
*/
class MyCount {
private String oid; //账号
private int cash; //账户余额
MyCount (String oid, int cash) {
this.oid = oid;
this.cash = cash;
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public int getCash () {
return cash;
}
public void setCash(int cash) {
this.cash = cash;
}
public String toString() {
return "MyCount {" +
"oid='" + oid + '\'' +
", cash=" + cash +
'}';
}
}
运行结果
A正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为-400,当前金额为1000
A操作MyCount {oid='11111111111111', cash=600}账户成功,金额为-400,当前金额为600
B正在操作MyCount {oid='11111111111111', cash=600}账户,金额为400,当前金额为600
B操作MyCount {oid='11111111111111', cash=1000}账户成功,金额为400,当前金额为1000
D正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为100,当前金额为1000
D操作MyCount {oid='11111111111111', cash=1100}账户成功,金额为100,当前金额为1100
C正在操作MyCount {oid='11111111111111', cash=1100}账户,金额为-600,当前金额为1100
C操作MyCount {oid='11111111111111', cash=500}账户成功,金额为-600,当前金额为500
Process finished with exit code 0
注意:在获取了锁对象后,用完后应该尽快释放锁,以便别的等待该锁的线程有机会去执行。
2、读写锁
Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock,也有具体的实现ReentrantReadWriteLock,详细的API可以查看JavaAPI文档。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author dong
* @function Java线程:锁
*/
public class Test {
public static void main(String[] args) {
//创建并发访问的账户
MyCount myCount = new MyCount("11111111111111", 1000);
//创建一个锁对象
ReadWriteLock lock = new ReentrantReadWriteLock();
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//创建一些并发访问用户,一个信用卡,存的存,取的取
User u1 = new User("A", myCount, -400, lock, false);
User u2 = new User("B", myCount, 400, lock, false);
User u3 = new User("C", myCount, -600, lock, false);
User u4 = new User("D", myCount, 100, lock, false);
User u5 = new User("B", myCount, 0, lock, true);
//在线程池中执行各个用户的操作
pool.execute(u1);
pool.execute(u2);
pool.execute(u3);
pool.execute(u4);
pool.execute(u5);
//关闭线程池
pool.shutdown();
}
}
/**
* 信用卡的用户
*/
class User implements Runnable {
private String name; //用户名
private MyCount myCount; //索要操作的账户
private int iocash; //操作的金额,当然有正负之分了
private ReadWriteLock myLock; //执行操作所需要的锁对象
private boolean ischeck; //是否查询
User (String name, MyCount myCount, int iocash, ReadWriteLock myLock, boolean ischeck) {
this.name = name;
this.myCount = myCount;
this.iocash = iocash;
this.myLock = myLock;
this.ischeck = ischeck;
}
public void run() {
if (ischeck) {
//获取读锁
myLock.readLock().lock();
System.out.println("读: " + name + "正在查询" + myCount + "账户,当前金额为" + myCount.getCash());
//释放读锁
myLock.readLock().unlock();
} else {
//获取写锁
myLock.writeLock().lock();
//执行现金业务
System.out.println("写: " + name + "正在操作" + myCount + "账户,金额为" +
iocash + ",当前金额为" + myCount.getCash());
myCount.setCash(myCount.getCash() + iocash);
System.out.println("写: " + name + "操作" + myCount + "账户,当前金额为" + myCount.getCash());
//释放写锁
myLock.writeLock().unlock();
}
}
}
/**
*信用卡账户,可随意透支
*/
class MyCount {
private String oid; //账号
private int cash; //账户余额
MyCount (String oid, int cash) {
this.oid = oid;
this.cash = cash;
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public int getCash () {
return cash;
}
public void setCash(int cash) {
this.cash = cash;
}
public String toString() {
return "MyCount {" +
"oid='" + oid + '\'' +
", cash=" + cash +
'}';
}
}
运行结果
写: A正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为-400,当前金额为1000
写: A操作MyCount {oid='11111111111111', cash=600}账户,当前金额为600
写: B正在操作MyCount {oid='11111111111111', cash=600}账户,金额为400,当前金额为600
写: B操作MyCount {oid='11111111111111', cash=1000}账户,当前金额为1000
写: C正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为-600,当前金额为1000
写: C操作MyCount {oid='11111111111111', cash=400}账户,当前金额为400
写: D正在操作MyCount {oid='11111111111111', cash=400}账户,金额为100,当前金额为400
写: D操作MyCount {oid='11111111111111', cash=500}账户,当前金额为500
读: B正在查询MyCount {oid='11111111111111', cash=500}账户,当前金额为500
Process finished with exit code 0
注意:在实际开发中,最好在能用读写锁的情况下使用读写锁,而不要用普通锁,以求更好的性能。
十三、信号量
信号量是一个功能完毕的计数器,对控制一定资源的消费与回收有着很重要的意义,信号量常常用于多线程的代码中,并能监控有多少数目的线程等待获取资源,并且通过信号量可以得知可用资源的数目等等,这里总是在强调“数目”二字,但不能指出来有哪些在等待,哪些资源可用。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author dong 2017-07-10
* @function Java线程:信号量
*/
public class Test {
public static void main(String[] args) {
MyPool myPool = new MyPool(20);
//创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
MyThread t1 = new MyThread("任务A", myPool, 3);
MyThread t2 = new MyThread("任务B", myPool, 12);
MyThread t3 = new MyThread("任务C", myPool, 7);
//在线程池中执行任务
threadPool.execute(t1);
threadPool.execute(t2);
threadPool.execute(t3);
//关闭池
threadPool.shutdown();
}
}
/**
* 一个线程池
*/
class MyPool {
private Semaphore sp; //池相关的信号量
/**
* 池的大小,这个大小会传递给信号量
* @param size 池的大小
*/
MyPool(int size) {
this.sp = new Semaphore(size);
}
public Semaphore getSp() {
return sp;
}
public void setSp(Semaphore sp) {
this.sp = sp;
}
}
/**
*信用卡账户,可随意透支
*/
class MyThread extends Thread {
private String threadname; //线程的名称
private MyPool pool; //自定义池
private int x; //申请信号量的大小
MyThread(String threadname, MyPool pool, int x) {
this.threadname = threadname;
this.pool = pool;
this.x = x;
}
public void run() {
try {
//从此信号量获取给定数目的许可
pool.getSp().acquire(x);
//todo: 也许这里可以做更复杂的业务
System.out.println(threadname + "成功获取了" + x + "个许可!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放给定数目的许可,将其返回到信号量。
pool.getSp().release(x);
System.out.println(threadname + "释放了" + x + "个许可!");
}
}
}
运行结果:
任务A成功获取了3个许可!
任务A释放了3个许可!
任务C成功获取了7个许可!
任务C释放了7个许可!
任务B成功获取了12个许可!
任务B释放了12个许可!
Process finished with exit code 0
注意:从结果可以看出,信号量仅仅是对池资源进行监控,但不保证线程的安全,因此,在使用时候,应该自己控制线程的安全访问池资源。
十四、阻塞队列
Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素的操作会被阻塞等待,直到有空位为止。同样,当队列为空时候,请求队列元素的操作同样会阻塞等待,直到有可用元素为止。有了这样的功能,就为多线程的排队等候的模型实现开辟了便捷通道,非常有用。
java.util.concurrent.BlockingQueue继承了java.util.Queue接口。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author dong 2017-07-11
* @function Java线程:阻塞队列
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
BlockingQueue bqueue = new ArrayBlockingQueue(20);
for (int i = 0; i < 30; i++) {
//将指定元素添加到此队列中,如果没有可用空间,将一直等待(如果有必要)。
try {
bqueue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("向阻塞队列中添加了元素:" + i);
}
System.out.println("程序到此运行结束,即将推出---");
}
}
运行结果
向阻塞队列中添加了元素:0
向阻塞队列中添加了元素:1
向阻塞队列中添加了元素:2
向阻塞队列中添加了元素:3
向阻塞队列中添加了元素:4
向阻塞队列中添加了元素:5
向阻塞队列中添加了元素:6
向阻塞队列中添加了元素:7
向阻塞队列中添加了元素:8
向阻塞队列中添加了元素:9
向阻塞队列中添加了元素:10
向阻塞队列中添加了元素:11
向阻塞队列中添加了元素:12
向阻塞队列中添加了元素:13
向阻塞队列中添加了元素:14
向阻塞队列中添加了元素:15
向阻塞队列中添加了元素:16
向阻塞队列中添加了元素:17
向阻塞队列中添加了元素:18
向阻塞队列中添加了元素:19
可以看出,输出到元素19时候,就一直处于等待状态,因为队列满了,程序阻塞了。
阻塞队列还有更多实现类,用来满足各种复杂的需求:ArrayBlockingQueue,
,
/**
* java线程:条件变量
* @author dong
*
*/
public class test6 {
public static void main(String[] args) {
//创建并发访问的账户
MyCount2 myCount = new MyCount2("123456", 10000);
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 = new SavaThread("张三", myCount, 3600);
Thread t2 = new DrawThread("李四", myCount, 2700);
Thread t3 = new SavaThread("王五", myCount, 600);
Thread t4 = new DrawThread("老张", myCount, 1300);
Thread t5 = new SavaThread("老牛", myCount, 500);
Thread t6 = new SavaThread("胖子", myCount, 800);
//执行各个线程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//关闭线程池
pool.shutdown();
}
}
/**
* 存款线程
*/
class SavaThread extends Thread {
private String name;//操作人
private MyCount2 myCount;//账户
private int x;//存款金额
public SavaThread(String name, MyCount2 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
public void run() {
myCount.saving(x, name);
}
}
/**
* 取款线程类
*/
class DrawThread extends Thread {
private String name;//操作人
private MyCount2 myCount;//账户
private int x;//存款金额
public DrawThread(String name, MyCount2 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
@Override
public void run() {
myCount.drawing(x, name);
}
}
/**
* 普通银行账户,不可透支
*/
class MyCount2 {
private String oid;//账号
private int cash;//账户余额
private Lock lock = new ReentrantLock();//账户锁
private Condition _sava = lock.newCondition();//存款条件
private Condition _draw = lock.newCondition();//取款条件
public MyCount2(String oid, int cash) {
super();
this.oid = oid;
this.cash = cash;
}
/**
* 存款
* @param x 操作金额
* @param name 操作人
*/
public void saving(int x, String name) {
lock.lock();//获取锁
if (x > 0) {
cash += x;//存款
System.out.println(name + "存款" + x + ",当前余额为:" + cash);
}
_draw.signalAll();//唤醒所有等待线程
lock.unlock();
}
/**
* 取款
* @param x 操作金额
* @param name 操作人
*/
public void drawing(int x, String name) {
lock.lock();//获取锁
try {
if (cash -x < 0) {
_draw.await();//阻塞取款操作
} else {
cash -= x;//取款
System.out.println(name + "取款:" + x + ",当前余额为:" + cash);
}
_sava.signalAll();//唤醒所有取款操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
}
,
LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue,具体的API差别也很小。
十五、条件变量
条件变量都实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全。
条件变量的出现是为了更精细控制线程等待与唤醒,在java5之前,线程的等待与唤醒依靠的是Object对象的wait()和notify()/notifyAll()方法,这样的处理不够精细。
一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signalAll()方法,又可以唤醒该条件下的等待的线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* java线程:条件变量
* @author dong
*
*/
public class test6 {
public static void main(String[] args) {
//创建并发访问的账户
MyCount2 myCount = new MyCount2("123456", 10000);
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 = new SavaThread("张三", myCount, 3600);
Thread t2 = new DrawThread("李四", myCount, 2700);
Thread t3 = new SavaThread("王五", myCount, 600);
Thread t4 = new DrawThread("老张", myCount, 1300);
Thread t5 = new SavaThread("老牛", myCount, 500);
Thread t6 = new SavaThread("胖子", myCount, 800);
//执行各个线程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//关闭线程池
pool.shutdown();
}
}
/**
* 存款线程
*/
class SavaThread extends Thread {
private String name;//操作人
private MyCount2 myCount;//账户
private int x;//存款金额
public SavaThread(String name, MyCount2 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
public void run() {
myCount.saving(x, name);
}
}
/**
* 取款线程类
*/
class DrawThread extends Thread {
private String name;//操作人
private MyCount2 myCount;//账户
private int x;//存款金额
public DrawThread(String name, MyCount2 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
@Override
public void run() {
myCount.drawing(x, name);
}
}
/**
* 普通银行账户,不可透支
*/
class MyCount2 {
private String oid;//账号
private int cash;//账户余额
private Lock lock = new ReentrantLock();//账户锁
private Condition _sava = lock.newCondition();//存款条件
private Condition _draw = lock.newCondition();//取款条件
public MyCount2(String oid, int cash) {
super();
this.oid = oid;
this.cash = cash;
}
/**
* 存款
* @param x 操作金额
* @param name 操作人
*/
public void saving(int x, String name) {
lock.lock();//获取锁
if (x > 0) {
cash += x;//存款
System.out.println(name + "存款" + x + ",当前余额为:" + cash);
}
_draw.signalAll();//唤醒所有等待线程
lock.unlock();
}
/**
* 取款
* @param x 操作金额
* @param name 操作人
*/
public void drawing(int x, String name) {
lock.lock();//获取锁
try {
if (cash -x < 0) {
_draw.await();//阻塞取款操作
} else {
cash -= x;//取款
System.out.println(name + "取款:" + x + ",当前余额为:" + cash);
}
_sava.signalAll();//唤醒所有取款操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
}
如果不使用锁和条件变量,实现上述功能,如下所示:
/**
* java线程:不用条件变量
* @author dong
*
*/
public class test7 {
public static void main(String[] args) {
//创建并发访问的账户
MyCount3 myCount = new MyCount3("123456", 10000);
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 = new SaveThread1("张三", myCount, 2000);
Thread t2 = new SaveThread1("李四", myCount, 3600);
Thread t3 = new DrawThread1("王五", myCount, 5000);
Thread t4 = new SaveThread1("老牛", myCount, 1100);
Thread t5 = new DrawThread1("老张", myCount, 1500);
//执行各个线程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
/**
* 存款线程
*/
class SaveThread1 extends Thread {
private String name;//操作人
private MyCount3 myCount;//账户
private int x;//存款金额
public SaveThread1(String name, MyCount3 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
@Override
public void run() {
myCount.saving(x, name);
}
}
/**
* 取款线程
*/
class DrawThread1 extends Thread {
private String name;//操作人
private MyCount3 myCount;//账户
private int x;//存款金额
public DrawThread1(String name, MyCount3 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
@Override
public void run() {
myCount.drawing(x, name);
}
}
/**
* 普通银行卡账户,不可透支
*/
class MyCount3 {
private String oid;//账号
private int cash;//账户余额
public MyCount3(String oid, int cash) {
super();
this.oid = oid;
this.cash = cash;
}
/**
* 存款
* @param x 操作金额
* @param name 操作金额
*/
public synchronized void saving(int x, String name) {
if (x > 0) {
cash += x;
System.out.println(name + "存款" + x + ",当前余额为:" + cash);
}
notifyAll();
}
/**
* 取款
* @param x 操作金额
* @param name 操作人
*/
public synchronized void drawing(int x, String name) {
if (cash - x < 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
cash -= x;//取款
System.out.println(name + "取款" + x + "当前余额为:" + cash);
}
}
}
用同步方式,实现上述功能,如下附件所示:
/**
* java线程:不用条件变量,改为同步代码块
* @author dong
*
*/
public class test8 {
public static void main(String[] args) {
//创建并发访问的账户
MyCount3 myCount = new MyCount3("123456", 10000);
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 = new SaveThread1("张三", myCount, 2000);
Thread t2 = new SaveThread1("李四", myCount, 3600);
Thread t3 = new DrawThread1("王五", myCount, 5000);
Thread t4 = new SaveThread1("老牛", myCount, 1100);
Thread t5 = new DrawThread1("老张", myCount, 1500);
//执行各个线程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
/**
* 存款线程
*/
class SaveThread2 extends Thread {
private String name;//操作人
private MyCount3 myCount;//账户
private int x;//存款金额
public SaveThread2(String name, MyCount3 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
@Override
public void run() {
myCount.saving(x, name);
}
}
/**
* 取款线程
*/
class DrawThread2 extends Thread {
private String name;//操作人
private MyCount3 myCount;//账户
private int x;//存款金额
public DrawThread2(String name, MyCount3 myCount, int x) {
super();
this.name = name;
this.myCount = myCount;
this.x = x;
}
@Override
public void run() {
myCount.drawing(x, name);
}
}
/**
* 普通银行卡账户,不可透支
*/
class MyCount4 {
private String oid;//账号
private int cash;//账户余额
public MyCount4(String oid, int cash) {
super();
this.oid = oid;
this.cash = cash;
}
/**
* 存款
* @param x 操作金额
* @param name 操作金额
*/
public synchronized void saving(int x, String name) {
if (x > 0) {
synchronized (this) {
cash += x;//存款
System.out.println(name + "存款" + x + ",当前余额为:" + cash);
notifyAll();//唤醒所有等待的线程
}
}
}
/**
* 取款
* @param x 操作金额
* @param name 操作人
*/
public synchronized void drawing(int x, String name) {
synchronized (this) {
if (cash - x < 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
cash -= x;//取款
System.out.println(name + "取款" + x + "当前余额为:" + cash);
}
notifyAll();//唤醒所有的存款操作
}
}
}
对比以上三种方式,从控制角度上讲,第一种最灵活,第二种代码最简单,第三种容易犯错。
十六、原子量
原子量的操作是“原子的”,该操作不可再分因此是线程安全的。在java5之前,可以通过volatile、synchronized关键字来解决并发访问的安全问题,但比较麻烦。java5之后提供了用来进行单变量多线程并发安全访问的工具包java.util.concurrent.atomic,其中的类也简单。
public class test9 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
Lock lock = new ReentrantLock(false);
Runnable t1 = new MyRunnable3("张三", 2000, lock);
Runnable t2 = new MyRunnable3("李四", 3600, lock);
Runnable t3 = new MyRunnable3("王五", 2700, lock);
Runnable t4 = new MyRunnable3("老张", 600, lock);
Runnable t5 = new MyRunnable3("老牛", 1300, lock);
Runnable t6 = new MyRunnable3("胖子", 800, lock);
//执行各个线程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//关闭线程池
pool.shutdown();
}
}
class MyRunnable3 implements Runnable {
private static AtomicLong aLong = new AtomicLong(1000);//原子量,每个线程都可以自由操作
private String name; //操作人
private int x;//操作数额
private Lock lock;
public MyRunnable3(String name, int x, Lock lock) {
super();
this.name = name;
this.x = x;
this.lock = lock;
}
@Override
public void run() {
lock.lock();
System.out.println(x + "执行了" + x + ",当前余额为:" + aLong.addAndGet(x));
lock.unlock();
}
}
有关原子量的用法很简单,关键是对原子量的认识,原子仅仅是保证变量操作的原子性,但整个程序还需要考虑线程安全的。
十七、障碍器
Java5中,添加了障碍器类,为了适应一种新的设计需求,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择障碍器了。
实例见如下:
/**
* java线程:障碍器
* @author dong
*
*/
public class test10 {
public static void main(String[] args) {
//创建障碍器,并设置MainTask为所有定数量的线程都达到障碍点的时候所要执行的任务(Runnable)
CyclicBarrier cb = new CyclicBarrier(7, new MainTask());
new SubTask("A", cb).start();
new SubTask("B", cb).start();
new SubTask("C", cb).start();
new SubTask("D", cb).start();
new SubTask("E", cb).start();
new SubTask("F", cb).start();
new SubTask("G", cb).start();
}
}
/**
* 主任务
*/
class MainTask implements Runnable {
@Override
public void run() {
System.out.println(">>>>>>主任务执行了<<<<<<<");
}
}
/**
*子任务
*/
class SubTask extends Thread {
private String name;
private CyclicBarrier cb;
public SubTask(String name, CyclicBarrier cb) {
super();
this.name = name;
this.cb = cb;
}
@Override
public void run() {
System.out.println("[子任务" + name + "]开始执行了!");
for (int i = 0; i < 99999; i++);//模拟耗时任务
System.out.println("[子任务" + name + "]开始执行完成了,并通过障碍器已经完成!");
try {
//通知障碍器已经完成
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}