并发编程----------线程中的共享与协作

线程中的共享与协作

线程中的共享

synchronized内置锁

线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步的执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立的运行,那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,包括数据之间的共享,协同处理事情。这将会带来巨大的价值。
Java支持多个线程同时访问一个对象或者对象的成员变量,关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。

对象锁和类锁

对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每一个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每一个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每各类的对应的class对象。类锁和对象锁之间也是也是互不干扰的。
对象锁和类锁,以及锁static变量之间的运行情况:

/**
 *类说明:synchronized关键字的使用方法
 */
public class SynTest {

	private long count =0;
	private Object obj = new Object();//作为一个锁

	public long getCount() {
		return count;
	}

	public void setCount(long count) {
		this.count = count;
	}

	/*用在同步块上*/
	public void incCount(){
		synchronized (obj){
			count++;
		}
	}

	/*用在方法上*/
	public synchronized void incCount2(){
			count++;
	}

	/*用在同步块上,但是锁的是当前类的对象实例*/
	public void incCount3(){
		synchronized (this){
			count++;
		}
	}

	//线程
	private static class Count extends Thread{

		private SynTest simplOper;

		public Count(SynTest simplOper) {
			this.simplOper = simplOper;
		}

		@Override
		public void run() {
			for(int i=0;i<10000;i++){
				simplOper.incCount();//count = count+10000
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		SynTest simplOper = new SynTest();
		//启动两个线程
		Count count1 = new Count(simplOper);
		Count count2 = new Count(simplOper);
		count1.start();
		count2.start();
		Thread.sleep(50);
		System.out.println(simplOper.count);//20000
	}
}
/**
 *类说明:锁的实例不一样,也是可以并行的
 */
public class DiffInstance {
	
    private static class InstanceSyn implements Runnable{
        private DiffInstance diffInstance;

        public InstanceSyn(DiffInstance diffInstance) {
            this.diffInstance = diffInstance;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is running..."+ diffInstance);
            diffInstance.instance();
        }
    }

    private static class Instance2Syn implements Runnable{
        private DiffInstance diffInstance;

        public Instance2Syn(DiffInstance diffInstance) {
            this.diffInstance = diffInstance;
        }
        @Override
        public void run() {
            System.out.println("TestInstance2 is running..."+ diffInstance);
            diffInstance.instance2();
        }
    }

    private synchronized void instance(){
        SleepTools.second(3);
        System.out.println("synInstance is going..."+this.toString());
        SleepTools.second(3);
        System.out.println("synInstance ended "+this.toString());
    }

    private synchronized void instance2(){
        SleepTools.second(3);
        System.out.println("synInstance2 is going..."+this.toString());
        SleepTools.second(3);
        System.out.println("synInstance2 ended "+this.toString());
    }

    public static void main(String[] args) {
        DiffInstance instance1 = new DiffInstance();
        Thread t3 = new Thread(new Instance2Syn(instance1));
        DiffInstance instance2 = new DiffInstance();
        Thread t4 = new Thread(new InstanceSyn(instance1));
        t3.start();
        t4.start();
        SleepTools.second(1);
    }
}
/**
 *类说明:演示实例锁和类锁是不同的,两者可以并行
 */
public class InstanceAndClass {
	
    private static class SynClass extends Thread{
        @Override
        public void run() {
            System.out.println("TestClass is running...");
            synClass();
        }
    }

    private static class InstanceSyn implements Runnable{
        private InstanceAndClass SynClassAndInstance;

        public InstanceSyn(InstanceAndClass SynClassAndInstance) {
            this.SynClassAndInstance = SynClassAndInstance;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is running..."+SynClassAndInstance);
            SynClassAndInstance.instance();
        }
    }

    private synchronized void instance(){
        SleepTools.second(1);
        System.out.println("synInstance is going..."+this.toString());
        SleepTools.second(1);
        System.out.println("synInstance ended "+this.toString());
    }

    private static synchronized void synClass(){
        SleepTools.second(1);
        System.out.println("synClass going...");
        SleepTools.second(1);
        System.out.println("synClass end");
    }

    public static void main(String[] args) {
        InstanceAndClass synClassAndInstance = new InstanceAndClass();
        Thread t1 = new SynClass();
        Thread t2 = new Thread(new InstanceSyn(synClassAndInstance));
        t2.start();
        SleepTools.second(1);
        t1.start();
    }
}
/**
 *类说明:类锁和锁static变量也是不同的
 */
public class StaticAndClass {
	
    private static class SynClass extends Thread{
        @Override
        public void run() {
            System.out.println(currentThread().getName()
                    +":SynClass is running...");
            synClass();
        }
    }

    private static class SynStatic extends Thread{
        @Override
        public void run() {
            System.out.println(currentThread().getName()
                    +"SynStatic is running...");
            synStatic();
        }
    }

    private static synchronized void synClass(){
        System.out.println(Thread.currentThread().getName()
                +"synClass going...");
        SleepTools.second(1);
        System.out.println(Thread.currentThread().getName()
                +"synClass end");
    }

    private static Object obj = new Object();
    private static void synStatic(){
        synchronized (obj){
            System.out.println(Thread.currentThread().getName()
                    +"synStatic going...");
            SleepTools.second(1);
            System.out.println(Thread.currentThread().getName()
                    +"synStatic end");
        }
    }

    public static void main(String[] args) {
        StaticAndClass synClassAndInstance = new StaticAndClass();
        Thread t1 = new SynClass();
        //Thread t2 = new SynStatic();
        Thread t2 = new SynClass();
        t2.start();
        SleepTools.second(1);
        t1.start();
    }
}

错误的加锁和原因分析

/**
 * 类说明:错误的加锁和原因分析
 */
public class TestIntegerSyn {

    public static void main(String[] args) throws InterruptedException {
        Worker worker=new Worker(1);
        //Thread.sleep(50);
        for(int i=0;i<5;i++) {
            new Thread(worker).start();
        }
    }

    private static class Worker implements Runnable{

        private Integer i;

        public Worker(Integer i) {
            this.i=i;
        }

        @Override
        public void run() {
            synchronized (i) {
                Thread thread=Thread.currentThread();
                System.out.println(thread.getName()+"--@"+System.identityHashCode(i));
                i++;
                System.out.println(thread.getName()+"-------"+i+"-@"+System.identityHashCode(i));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName()+"-------"+i+"--@"+System.identityHashCode(i));
            }
        }
    }
}

原因:虽然我们对 i 进行了加锁,但是
在这里插入图片描述

但是当我们反编译这个类的 class 文件后,可以看到 i++实际是,
在这里插入图片描述
在这里插入图片描述
本质上是返回了一个新的 Integer 对象。也就是每个线程实际加锁的是不同 的 Integer 对象。

volatile,最轻量的同步机制

volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某 个变量的值,这新值对其他线程来说是立即可见的。参见代码:

/**
 * 类说明:演示Volatile的提供的可见性
 */
public class VolatileCase {
    private volatile static boolean ready;
    private static int number;

    //
    private static class PrintThread extends Thread{
        @Override
        public void run() {
            System.out.println("PrintThread is running.......");
            while(!ready);//无限循环
            System.out.println("number = "+number);
        }
    }

    public static void main(String[] args) {
        new PrintThread().start();
        SleepTools.second(1);
        number = 51;
        ready = true;
        SleepTools.second(5);
        System.out.println("main is ended!");
    }
}

不加 volatile 时,子线程无法感知主线程修改了 ready 的值,从而不会退出循环, 而加了 volatile 后,子线程可以感知主线程修改了 ready 的值,迅速退出循环。 但是 volatile 不能保证数据在多个线程下同时写时的线程安全,参见代码:

/**
 * 类说明:
 */
public class NotSafe {
    private volatile long count =0;

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    //count进行累加
    public void incCount(){
        count++;
    }

    //线程
    private static class Count extends Thread{

        private NotSafe simplOper;

        public Count(NotSafe simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                simplOper.incCount();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NotSafe simplOper = new NotSafe();
        //启动两个线程
        Count count1 = new Count(simplOper);
        Count count2 = new Count(simplOper);
        count1.start();
        count2.start();
        Thread.sleep(50);
        System.out.println(simplOper.count);//20000?
    }
}

volatile 最适用的场景:一个线程写,多个线程读。

ThreadLocal辨析

与Synchronized的比较

ThreadLocal 和 Synchonized 都用于解决多线程并发訪问。可是 ThreadLocal 与 synchronized 有本质的差别。synchronized 是利用锁的机制,使变量或代码块 在某一时该仅仅能被一个线程訪问。而 ThreadLocal 为每个线程都提供了变量的 副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线 程对数据的数据共享。
Spring 的事务就借助了 ThreadLocal 类。Spring 会从数据库连接池中获得一个 connection,然会把 connection 放进 ThreadLocal 中,也就和线程绑定了,事务需 要提交或者回滚,只要从 ThreadLocal 中拿到 connection 进行操作。为何 Spring 的事务要借助 ThreadLocal 类?
以 JDBC 为例,正常的事务代码可能如下:
dbc = new DataBaseConnection();//第 1 行
Connection con = dbc.getConnection();//第 2 行
con.setAutoCommit(false);// //第 3 行 con.executeUpdate(…);//第 4 行
con.executeUpdate(…);//第 5 行
con.executeUpdate(…);//第 6 行
con.commit();第 7 行
上述代码,可以分成三个部分:
事务准备阶段:第 1~3 行
业务处理阶段:第 4~6 行
事务提交阶段:第 7 行
可以很明显的看到,不管我们开启事务还是执行具体的 sql 都需要一个具体 的数据库连接。
现在我们开发应用一般都采用三层结构,如果我们控制事务的代码都放在 DAO(DataAccessObject)对象中,在 DAO 对象的每个方法当中去打开事务和关闭 事务,当 Service 对象在调用 DAO 时,如果只调用一个 DAO,那我们这样实现则 效果不错,但往往我们的 Service 会调用一系列的 DAO 对数据库进行多次操作, 那么,这个时候我们就无法控制事务的边界了,因为实际应用当中,我们的 Service 调用的 DAO 的个数是不确定的,可根据需求而变化,而且还可能出现 Service 调 用 Service 的情况。
如果不使用 ThreadLocal,代码大概就会是这个样子:
在这里插入图片描述

但是需要注意一个问题,如何让三个 DAO 使用同一个数据源连接呢?我们 就必须为每个 DAO 传递同一个数据库连接,要么就是在 DAO 实例化的时候作为 构造方法的参数传递,要么在每个 DAO 的实例方法中作为方法的参数传递。这 两种方式无疑对我们的 Spring 框架或者开发人员来说都不合适。为了让这个数据 库连接可以跨阶段传递,又不显示的进行参数传递,就必须使用别的办法。
Web 容器中,每个完整的请求周期会由一个线程来处理。因此,如果我们 能将一些参数绑定到线程的话,就可以实现在软件架构中跨层次的参数共享(是 隐式的共享)。而 JAVA 中恰好提供了绑定的方法–使用 ThreadLocal。
结合使用 Spring 里的 IOC 和 AOP,就可以很好的解决这一点。
只要将一个数据库连接放入 ThreadLocal 中,当前线程执行时只要有使用数 据库连接的地方就从 ThreadLocal 获得就行了。

ThreadLocal的使用

ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:
• void set(Object value)
设置当前线程的线程局部变量的值。
• public Object get()
该方法返回当前线程所对应的线程局部变量。
• public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它 可以加快内存回收的速度。
• protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null。
public final static ThreadLocal RESOURCE = new ThreadLocal();
RESOURCE代表一个能够存放String类型的ThreadLocal对象。 此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是 线程安全的。

实现解析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面先取到当前线程,然后调用 getMap 方法获取对应的 ThreadLocalMap, ThreadLocalMap 是 ThreadLocal 的静态内部类,然后 Thread 类中有一个这样类型 成员,所以 getMap 是直接返回 Thread 的成员。 看下 ThreadLocal 的内部类 ThreadLocalMap 源码:
在这里插入图片描述
可以看到有个 Entry 内部静态类,它继承了 WeakReference,总之它记录了 两个信息,一个是 ThreadLocal<?>类型,一个是 Object 类型的值。getEntry 方法 则是获取某个 ThreadLocal 对应的值,set 方法就是更新或赋值相应的 ThreadLocal 对应的值。
在这里插入图片描述
在这里插入图片描述
回顾我们的 get 方法,其实就是拿到每个线程独有的 ThreadLocalMap 然后再用 ThreadLocal 的当前实例,拿到 Map 中的相应的 Entry,然后就可 以拿到相应的值返回出去。当然,如果 Map 为空,还会先进行 map 的创建,初 始化等工作。

引发的内存泄露分析

		引用
	Object o = new Object();
	这个 o,我们可以称之为对象引用,而 new Object()我们可以称之为在内存 中产生了一个对象实例。

在这里插入图片描述
当写下 o=null 时,只是表示 o 不再指向堆中 object 的对象实例,不代表这 个对象实例不存在了。
强引用就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例。
软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。
弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。
虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。

内存泄露的现象

执行 cn.enjoyedu.ch1.threadlocal 下的 ThreadLocalOOM,并将堆内存大小设 置为-Xmx256m, 我们启用一个线程池,大小固定为 5 个线程
在这里插入图片描述
首先只简单的在每个任务中 new 出一个数组
在这里插入图片描述
可以看到内存的实际使用控制在 25M 左右:因为每个任务中会不断 new 出 一个 5M 的数组,5*5=25M,这是很合理的。
在这里插入图片描述
当我们启用了 ThreadLocal 以后:
在这里插入图片描述
在这里插入图片描述
内存占用最高升至 150M,一般情况下稳定在 90M 左右,那么加入一个 ThreadLocal 后,内存的占用真的会这么多?
于是,我们加入一行代码:
在这里插入图片描述
再执行,看看内存情况:
在这里插入图片描述
可以看见最高峰的内存占用也在 25M 左右,完全和我们不加 ThreadLocal 表 现一样。
这就充分说明,确实发生了内存泄漏。

分析

根据我们前面对 ThreadLocal 的分析,我们可以知道每个 Thread 维护一个 ThreadLocalMap,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需 要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。仔细观察 ThreadLocalMap,这个 map 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
因此使用了 ThreadLocal 后,引用链如图所示
在这里插入图片描述
图中的虚线表示弱引用。
这样,当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前 线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强 引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块 value 永 远不会被访问到了,所以存在着内存泄露。
只有当前 thread 结束以后,current thread 就不会存在栈中,强引用断开, Current Thread、Map value 将全部被 GC 回收。最好的做法是不在需要使用 ThreadLocal 变量后,都调用它的 remove()方法,清除数据。
其实考察 ThreadLocal 的实现,我们可以看见,无论是 get()、set()在某些时 候,调用了 expungeStaleEntry 方法用来清除 Entry 中 Key 为 null 的 Value,但是 这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。 只有 remove()方法中显式调用了 expungeStaleEntry 方法。
从表面上看内存泄漏的根源在于使用了弱引用,但是另一个问题也同样值得 思考:为什么使用弱引用而不是强引用?
下面我们分两种情况讨论:
key 使用强引用:引用 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删除,ThreadLocal 的对象实例不会 被回收,导致 Entry 内存泄漏。
key 使用弱引用:引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 的对象实例也会被 回收。value 在下一次 ThreadLocalMap 调用 set,get,remove 都有机会被回收。
比较两种情况,我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可 以多一层保障。
因此,ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏,而不是因为弱引 用。

总结

JVM 利用设置 ThreadLocalMap 的 Key 为弱引用,来避免内存泄露。
JVM 利用调用 remove、get、set 方法的时候,回收弱引用。
当 ThreadLocal 存储很多 Key 为 null 的 Entry 的时候,而不再去调用 remove、 get、set 方法,那么将导致内存泄漏。
使用线程池+ ThreadLocal 时要小心,因为这种情况下,线程是一直在不断的 重复运行的,从而也就造成了 value 可能造成累积的情况。

错误使用ThreadLocal导致线程不安全

	参见代码:
/**
 * 类说明:ThreadLocal的线程不安全演示
 */
public class ThreadLocalUnsafe implements Runnable {

    public Number number = new Number(0);

    public void run() {
        //每个线程计数加一
        number.setNum(number.getNum()+1);
      //将其存储到ThreadLocal中
        value.set(number);
        SleepTools.ms(2);
        //输出num值
        System.out.println(Thread.currentThread().getName()+"="+value.get().getNum());
    }

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadLocalUnsafe()).start();
        }
    }

    private static class Number {
        public Number(int num) {
            this.num = num;
        }

        private int num;

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return "Number [num=" + num + "]";
        }
    }
}

运行后的结果为:
在这里插入图片描述
如果我们加入 SleepTools.ms(2);会看的更明显
在这里插入图片描述

为什么每个线程都输出 5?难道他们没有独自保存自己的 Number 副本吗? 为什么其他线程还是能够修改这个值?仔细考察 ThreadLocal 和 Thead 的代码, 我们发现 ThreadLocalMap 中保存的其实是对象的一个引用,这样的话,当有其 他线程对这个引用指向的对象实例做修改时,其实也同时影响了所有的线程持有 的对象引用所指向的同一个对象实例。这也就是为什么上面的程序为什么会输出
一样的结果:5 个线程中保存的是同一 Number 对象的引用,在线程睡眠的时候, 其他线程将 num 变量进行了修改,而修改的对象 Number 的实例是 同一份,因 此它们最终输出的结果是相同的。
而上面的程序要正常的工作,应该的用法是让每个线程中的 ThreadLocal 都 应该持有一个新的 Number 对象。

线程间的协作

线程之间相互配合,完成某项工作,比如:一个线程修改了一个对象的值, 而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程, 而最终执行又是另一个线程。前者是生产者,后者就是消费者,这种模式隔离了 “做什么”(what)和“怎么做”(How),简单的办法是让消费者线程不断地 循环检查变量是否符合预期在 while 循环中设置不满足的条件,如果条件满足则 退出 while 循环,从而完成消费者的工作。却存在如下问题:
1) 难以确保及时性。
2)难以降低开销。如果降低睡眠的时间,比如休眠 1 毫秒,这样消费者能 更加迅速地发现条件变化,但是却可能消耗更多的处理器资源,造成了无端的浪 费。

等待/通知机制

是指一个线程 A 调用了对象 O 的 wait()方法进入等待状态,而另一个线程 B 调用了对象 O 的 notify()或者 notifyAll()方法,线程 A 收到通知后从对象 O 的 wait() 方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象 上的 wait()和 notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通 知方之间的交互工作。
notify():
通知一个在对象上等待的线程,使其从 wait 方法返回,而返回的前提是该线程 获取到了对象的锁,没有获得锁的线程重新进入 WAITING 状态。
notifyAll():
通知所有等待在该对象上的线程
wait() :
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断 才会返回.需要注意,调用 wait()方法后,会释放对象的锁
wait(long) :
超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n 毫秒,如果没有 通知就超时返回
wait (long,int) :
对于超时时间更细粒度的控制,可以达到纳秒

等待和通知的标准范式

等待方遵循如下原则。
1)获取对象的锁。
2)如果条件不满足,那么调用对象的 wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
在这里插入图片描述
通知方遵循如下原则。
1)获得对象的锁。
2)改变条件。
3)通知所有等待在对象上的线程。
在这里插入图片描述
在调用 wait()、notify()系列方法之前,线程必须要获得该对象的对象级 别锁,即只能在同步方法或同步块中调用 wait()方法、notify()系列方法,进 入 wait()方法后,当前线程释放锁,在从 wait()返回前,线程与其他线程竞 争重新获得锁,执行 notify()系列方法的线程退出调用了 notifyAll 的 synchronized 代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象锁,它就会 继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的 线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

notify 和 notifyAll 应该用谁

尽可能用 notifyall(),谨慎使用 notify(),因为 notify()只会唤醒一个线程,我 们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程,具体表现参见代码。

等待超时模式实现一个连接池

调用场景:调用一个方法时等待一段时间(一般来说是给定一个时间段), 如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超 时返回默认结果。
假设等待时间段是 T,那么可以推断出在当前时间 now+T 之后就会超时
等待持续时间:REMAINING=T。
•超时时间:FUTURE=now+T。
// 对当前对象加锁
public synchronized Object get(long mills) throws InterruptedException {
long future = System.currentTimeMillis() + mills;
long remaining = mills;
// 当超时大于 0 并且 result 返回值不满足要求
while ((result == null) && remaining > 0) {
wait(remaining);
remaining = future - System.currentTimeMillis();
}
return result;
}

具体实现参见:包下 cn.enjoyedu.ch1.pool 的代码

/**
 *类说明:连接池的实现
 */
public class DBPool {

    private static LinkedList<Connection> pool = new LinkedList<Connection>();

    public DBPool(int initialSize) {
        if (initialSize > 0) {
            for (int i = 0; i < initialSize; i++) {
                pool.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }

    public void releaseConnection(Connection connection) {
        if (connection != null) {
            //TODO
        }
    }

    // 在mills内无法获取到连接,将会返回null
    public Connection fetchConnection(long mills) throws InterruptedException {
        //TODO
        return null;
    }
}
/**
 *类说明:
 */
public class DBPoolTest {
    static DBPool pool  = new DBPool(10);
    // 控制器:控制main线程将会等待所有Woker结束后才能继续执行
    static CountDownLatch end;

    public static void main(String[] args) throws Exception {
    	// 线程数量
        int threadCount = 50;
        end = new CountDownLatch(threadCount);
        int count = 20;//每个线程的操作次数
        AtomicInteger got = new AtomicInteger();//计数器:统计可以拿到连接的线程
        AtomicInteger notGot = new AtomicInteger();//计数器:统计没有拿到连接的线程
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new Worker(count, got, notGot), 
            		"worker_"+i);
            thread.start();
        }
        end.await();// main线程在此处等待
        System.out.println("总共尝试了: " + (threadCount * count));
        System.out.println("拿到连接的次数:  " + got);
        System.out.println("没能连接的次数: " + notGot);
    }

    static class Worker implements Runnable {
        int           count;
        AtomicInteger got;
        AtomicInteger notGot;

        public Worker(int count, AtomicInteger got,
                               AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }

        public void run() {
            while (count > 0) {
                try {
                    // 从线程池中获取连接,如果1000ms内无法获取到,将会返回null
                    // 分别统计连接获取的数量got和未获取到的数量notGot
                    Connection connection = pool.fetchConnection(1000);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                        System.out.println(Thread.currentThread().getName()
                        		+"等待超时!");
                    }
                } catch (Exception ex) {
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}
/**
 *类说明:
 */
public class SqlConnectImpl implements Connection{
	
	/*拿一个数据库连接*/
    public static final Connection fetchConnection(){
        return new SqlConnectImpl();
    }

	@Override
	public boolean isWrapperFor(Class<?> arg0) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public <T> T unwrap(Class<T> arg0) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void abort(Executor arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void clearWarnings() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void close() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void commit() throws SQLException {
		SleepTools.ms(70);
	}

	@Override
	public Array createArrayOf(String arg0, Object[] arg1) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Blob createBlob() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Clob createClob() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public NClob createNClob() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public SQLXML createSQLXML() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Statement createStatement() throws SQLException {
		SleepTools.ms(1);
		return null;
	}

	@Override
	public Statement createStatement(int arg0, int arg1) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Statement createStatement(int arg0, int arg1, int arg2) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Struct createStruct(String arg0, Object[] arg1) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean getAutoCommit() throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public String getCatalog() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Properties getClientInfo() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String getClientInfo(String arg0) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getHoldability() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public DatabaseMetaData getMetaData() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getNetworkTimeout() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public String getSchema() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getTransactionIsolation() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public Map<String, Class<?>> getTypeMap() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public SQLWarning getWarnings() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isClosed() throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean isReadOnly() throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean isValid(int arg0) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public String nativeSQL(String arg0) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public CallableStatement prepareCall(String arg0) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public CallableStatement prepareCall(String arg0, int arg1, int arg2) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public CallableStatement prepareCall(String arg0, int arg1, int arg2, int arg3) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public PreparedStatement prepareStatement(String arg0) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public PreparedStatement prepareStatement(String arg0, int arg1) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public PreparedStatement prepareStatement(String arg0, int[] arg1) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public PreparedStatement prepareStatement(String arg0, String[] arg1) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public PreparedStatement prepareStatement(String arg0, int arg1, int arg2) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public PreparedStatement prepareStatement(String arg0, int arg1, int arg2, int arg3) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void releaseSavepoint(Savepoint arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void rollback() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void rollback(Savepoint arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setAutoCommit(boolean arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setCatalog(String arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setClientInfo(Properties arg0) throws SQLClientInfoException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setClientInfo(String arg0, String arg1) throws SQLClientInfoException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setHoldability(int arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setNetworkTimeout(Executor arg0, int arg1) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setReadOnly(boolean arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Savepoint setSavepoint() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Savepoint setSavepoint(String arg0) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setSchema(String arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setTransactionIsolation(int arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setTypeMap(Map<String, Class<?>> arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}
	
}

客户端获取连接的过程被设定为等待超时的模式,也就是在 1000 毫秒内如 果无法获取到可用连接,将会返回给客户端一个 null。设定连接池的大小为 10 个,然后通过调节客户端的线程数来模拟无法获取连接的场景。
它通过构造函数初始化连接的最大上限,通过一个双向队列来维护连接,调 用方需要先调用 fetchConnection(long)方法来指定在多少毫秒内超时获取连接, 当连接使用完成后,需要调用 releaseConnection(Connection)方法将连接放回线程 池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值