欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java线程

程序员文章站 2022-05-04 17:11:18
...
一、简介
描述
进程(process):是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。
线程(thread):进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。可以看成是轻量级的进程,是CPU调度和分派的基本单位。
区别
1、调度 :从上面的定义可以看出一个是调度和分派的基本单位,一个是拥有资源的基本单位
2、共享地址空间,资源:进程拥有各自独立的地址空间,资源,所以共享复杂,需要用IPC,同步简单; 线程共享所属进程的资源,共享简单,但同步复杂,要通过加锁等措施。
3、占用内存,cpu: 进程占用内存多,切换复杂,CPU利用率低; 线程占用内存少,切换简单,CPU利用率高。
4、相互影响: 进程间不会相互影响; 一个线程挂掉会导致整个进程挂掉。
二、线程的生命周期及五种基本状态
关于Java中线程的生命周期图
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();
		}
	}
}
相关标签: java线程