java线程

一、简介
描述
进程(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();
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值