精华内容
下载资源
问答
  • java监听机制原理-回调机制

    千次阅读 2016-07-28 12:18:02
    java me中,可以对一个Button添加一个鼠标点击事件,可以对一个文本框添加一个文本变化事件等等;在Android开发中,也有大量这种基于事件的处理。 探究其基本原理,则是对方法的回调。具体的看下面简单的代码即可...
    在java me中,可以对一个Button添加一个鼠标点击事件,可以对一个文本框添加一个文本变化事件等等;在Android开发中,也有大量这种基于事件的处理。
    探究其基本原理,则是对方法的回调。具体的看下面简单的代码即可了解

    首先创建一个接口:TextListener,接口内有一个名叫exchute()的方法

    public interface TextListener {

       

        public void excute();

     

    }

    可以看到,接口中并没有excute()方法的具体实现。


    接下来创建一个类:Text,这个类中包含了几个东西:第一个,TextListener的集合,因为可以为Text添加多个TextListener监听,因此,用一个集合来保存所有的监听;第二个addTextListener(TextListener tl),这个方法是给Text注册监听的,说的通俗一点,就是把一个新的监听添加到TextListener集合中去;第三个方法removeListener(TextListener tl),顾名思义,就是把某个监听从监听集合中移除,第三个方法叫excute(),这里不要和TextListener中的excute()方法混淆了,这个方法的作用就是回调注册了的TextListener,执行其中的代码。


    import java.util.List;


    import java.util.Vector;


     


    public class Text {


       


        private List listeners = new Vector<>();


       


        public Text() {


        }


       


        public void addTextListener(TextListener listener) {


           listeners.add(listener);


        }


       


        public void removeTextListener(TextListener listener) {


           listeners.remove(listener);


        }


       


        public void excute() {


           for (TextListener t : listeners) {


               t.excute();


           }


        }

    }

    注:Text中的excute()方法,实际上就是遍历监听集合中的所有元素,依次调用TextListener的excute()方法。额。。。希望没有把这两个excute方法混淆了,命名可以随便改。


    然后,写一个main函数,来运行着写代码:




    public static void main(String[] args) {

           Text text = new Text();

           text.addTextListener(new TextListener() {


               @Override

               public void excute() {

                  System.out.println("cc is better man");

               }

           });


           text.addTextListener(new TextListener() {


               @Override

               public void excute() {

                  int size = 100;

                  int rst = 0;

                  for (int i = 0; i < size; i++) {

                      rst += i;

                  }

                  System.out.println("the sum of 0 to 100 is :" + rst);

               }

           });

           text.excute();

        }

        说明:首先创建了一个Text对象,再为Text对象添加了两个TextListener的监听,每个监听里面实现的excute()方法不一样,最后,再调用text.excute()方法,就会依次执行注册了的监听。在这个地方,由于text中,监听集合实际上是一个接口的集合,Text并不知道调用接口的excute()方法后,会执行什么代码,在注册的时候,接口的excute()方法的实现,也不会执行,所以,为text添加监听的时候,text也不知道自己在执行excute()方法的时候,输出结果是什么。

         对于这种基于注册机制,来调用接口方法的用法,就是回调机制。

         按道理来讲,在添加监听的时候,是实现了一个接口的,也就是说,在这个时候,创建了一个接口的实例,并且将这个实例放在内存中了,text的addTextListener方法,实际上存了一个这个接口实例的一个引用地址。当执行text.excute()方法的时候,会根据注册的TextListener的内存地址,来找到这个监听接口的实例,然后再调用TextListener的excute方法。

         其实,上述代码还有一种写法,那就是创建一个类,实现TextListener接口的excute()方法,然后,new一个这个类,把这个类注册到text的监听里面,也能够实现同样的效果。

    代码如下:


    public class TextListenerImpl implements TextListener {

        @Override

        public void excute() {

           System.out.println("回调了这个方法~~~~~");

        }

    }


    text.addTextListener(new TextListenerImpl());//此为注册方法

    其实这样子看的话,会比较清晰,当然,这个TextListener的实现类中的excute方法具体要执行什么,text依然不知道。

    不知道这样子有没有说清楚。先这样吧。







    展开全文
  • 是多线程循环还是中断机制,或者是其他的?应该是类似嵌入式开发中的键盘中断处理模式吧?
  • Java 事件监听原理及Demo实现

    千次阅读 2016-12-11 18:12:33
    Java的事件监听Java事件机制的实现,以Java Swing的事件机制为例,分析一下事件监听的各个组成部分以及功能: Java事件机制的三个基本组成成分 1.事件对象 通常继承自java.util.EventObject的对象,一般可以用来...

    简介

    Java的事件监听是Java事件机制的实现,以Java Swing的事件机制为例,分析一下事件监听的各个组成部分以及功能:


    Java事件机制的三个基本组成成分

    1.事件对象

    通常继承自java.util.EventObject的对象,一般可以用来判断事件类型的作用

    2.事件源

    触发事件的源头,在GUI和Swing编程中,如Button按钮等对象就是一个事件源

    3.事件监听器

    负责监听事件源发出的事件并作出响应动作的对象,通常实现java.util.EventListener接口

    事件的一般流程

    1.事件源注册监听器

    2.事件发生,事件源向监听器发送事件对象

    3.监听器对象响应事件


    实现

    下面用Demo来介绍事件机制的实现原理

    1.事件对象类

    import java.util.EventObject;
    
    /**
     * Created by Xuyh at 2016/11/30 下午 04:00.
     *
     * <pre>
     *     自己理解事件对象的作用:
     *     (1)事件对象储存了事件源对象,
     *     当事件监听器方法进行处理程序的时候,
     *     如果需要改变事件源的属性值等等操作时候,
     *     就可以通过事件对象获取到事件源对象,
     *     从而对事件事件源的属性进行修改或者调用事件源的方法。
     *     (2)继承了java.util.EventObject之后的自定义事件对象,
     *     可以添加其他属性,比如说事件类型等等,
     *     添加一些必要的业务属性等。
     *
     * </pre>
     */
    public class JohnsonEventObject extends EventObject {
    	public static int EVENT_TYPE_ONE = 1;
    	public static int EVENT_TYPE_TWO = 2;
    	public static int EVENT_TYPE_THREE = 3;
    	private int eventType;
    
    	/**
    	 * source是事件源
    	 *
    	 * @param source
    	 */
    	public JohnsonEventObject(Object source) {
    		super(source);
    	}
    
    	public JohnsonEventObject(Object source, int eventType) {
    		super(source);
    		this.eventType = eventType;
    	}
    
    	@Override
    	public Object getSource() {
    		return super.getSource();
    	}
    
    	@Override
    	public String toString() {
    		return super.toString();
    	}
    
    	public int getEventType() {
    		return eventType;
    	}
    
    	public void setEventType(int eventType) {
    		this.eventType = eventType;
    	}
    }
    

    在该事件对象类中,除了默认继承自java.util.EventObject的source(即事件源对象属性)之外,另外添加了一个eventType(事件类型属性),用来代表事件的类型。

    当事件发生后,事件监听器的响应方法会接受到事件对象(作为方法参数被传递),事件对象中的事件源属性可以提供数据源对象供响应方法调用,从而改变事件源的属性等。


    2.事件源类

    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * Created by Xuyh at 2016/11/30 下午 04:00.
     *
     * <pre>
     *     事件源是事件发生的地方,
     *     是事件触发的源头,
     *     事件监听器注册的地方。
     * </pre>
     */
    public class JohnsonEventSource {
    	/**
    	 * 事件监听器的集合
    	 * 
    	 * <pre>
    	 *     事件源中存储一个监听器集合,
    	 *     当事件发生之后,事件源会依次调用
    	 *     每个监听器的响应方法。
    	 * </pre>
    	 */
    	private Set<JohnsonEventListener> eventListeners = new HashSet<JohnsonEventListener>();
    
    	/**
    	 * 注册监听器
    	 * 
    	 * <pre>
    	 *     理解:
    	 *     添加监听器
    	 * </pre>
    	 *
    	 * @param eventListener
    	 */
    	public void addEventListener(JohnsonEventListener eventListener) {
    		if (eventListener != null) {
    			eventListeners.add(eventListener);
    		}
    	}
    
    	/**
    	 * 注销监听器
    	 * 
    	 * <pre>
    	 *     理解:
    	 *     把这个监听器去掉
    	 * </pre>
    	 *
    	 * @param eventListener
    	 * @return
    	 */
    	public boolean removeEventListener(JohnsonEventListener eventListener) {
    		return eventListeners.remove(eventListener);
    	}
    
    	/**
    	 * 发生事件1
    	 * 
    	 * <pre>
    	 * 发生事件方法需要外部调用
    	 * </pre>
    	 */
    	public void event1() {
    		JohnsonEventObject object = new JohnsonEventObject(this, JohnsonEventObject.EVENT_TYPE_ONE);
    		doOnAction(object);
    	}
    
    	/**
    	 * 发生事件2
    	 * 
    	 * <pre>
    	 * 发生事件方法需要外部调用
    	 * </pre>
    	 */
    	public void event2() {
    		JohnsonEventObject object = new JohnsonEventObject(this, JohnsonEventObject.EVENT_TYPE_TWO);
    		doOnAction(object);
    	}
    
    	/**
    	 * 发生事件3
    	 * 
    	 * <pre>
    	 * 发生事件方法需要外部调用
    	 * </pre>
    	 */
    	public void event3() {
    		JohnsonEventObject object = new JohnsonEventObject(this, JohnsonEventObject.EVENT_TYPE_THREE);
    		doOnAction(object);
    	}
    
    	/**
    	 * 遍历监听器执行响应方法
    	 * 
    	 * @param eventObject
    	 */
    	private void doOnAction(JohnsonEventObject eventObject) {
    		for (JohnsonEventListener listener : eventListeners) {
    			listener.onAction(eventObject);
    		}
    	}
    
    	/**
    	 * <pre>
    	 * 定义一个事件源方法,
    	 * 给事件监听器调用,
    	 * 用来体现事件对象保存事件源的作用。
    	 * </pre>
    	 * 
    	 * @param message
    	 */
    	public void sourceFunction(String message) {
    		System.out.println(message);
    	}
    }
    
    事件源是事件触发的地方,其中有一个事件监听器集合,通过addEventListener方法可以添加事件监听器到监听器集合中,也就是经常说的“注册事件监听器”。

    其中的doOnAction方法是必不可少的一个操作,即在事件发生之后遍历监听器集合,执行监听器定义的动作响应方法。

    event1,event2,event3是定义的三个事件方法,模拟事件的发生,发生事件之后程序需要实例化事件对象(事件对象储存了事件源即本对象的引用),并将其作为参数依次调用每个监听器的动作响应方法,即上面的步骤。


    3.事件监听器类

    import java.util.EventListener;
    
    /**
     * Created by Xuyh at 2016/11/30 下午 04:00.
     *
     * <pre>
     *     事件监听器定义了一个接口,
     *     用来规范事件发生之后事件源调用的处理方法
     * </pre>
     */
    public interface JohnsonEventListener extends EventListener {
    	/**
    	 * 事件产生需要调用的方法
    	 */
    	public void onAction(JohnsonEventObject eventObject);
    }
    
    事件监听器是一个接口,里面定义了一个固定名字的响应方法,用来提供给事件源调用。

    注册事件监听器就是实现监听器接口的响应方法并添加到事件源的监听器集合中。


    其中,事件对象继承自java.util.EventObject ,源码如下

    package java.util;
    
    /**
     * <p>
     * The root class from which all event state objects shall be derived.
     * <p>
     * All Events are constructed with a reference to the object, the "source",
     * that is logically deemed to be the object upon which the Event in question
     * initially occurred upon.
     *
     * @since JDK1.1
     */
    
    public class EventObject implements java.io.Serializable {
    
        private static final long serialVersionUID = 5516075349620653480L;
    
        /**
         * The object on which the Event initially occurred.
         */
        protected transient Object  source;
    
        /**
         * Constructs a prototypical Event.
         *
         * @param    source    The object on which the Event initially occurred.
         * @exception  IllegalArgumentException  if source is null.
         */
        public EventObject(Object source) {
            if (source == null)
                throw new IllegalArgumentException("null source");
    
            this.source = source;
        }
    
        /**
         * The object on which the Event initially occurred.
         *
         * @return   The object on which the Event initially occurred.
         */
        public Object getSource() {
            return source;
        }
    
        /**
         * Returns a String representation of this EventObject.
         *
         * @return  A a String representation of this EventObject.
         */
        public String toString() {
            return getClass().getName() + "[source=" + source + "]";
        }
    }
    

    事件监听类实现java.util.EventListener 接口,源码如下:

    package java.util;
    
    /**
     * A tagging interface that all event listener interfaces must extend.
     * @since JDK1.1
     */
    public interface EventListener {
    }
    


    使用

    定义好了事件监听Demo之后就可以测试使用了,下面根据这个Demo来实现几种事件监听的实现方式:

    1.自身类作为事件监听器 :

    import event.JohnsonEventListener;
    import event.JohnsonEventObject;
    import event.JohnsonEventSource;
    
    /**
     * 自身类作为事件监听器
     * 
     * @Author Xuyh created at 2016年12月11日 下午5:57:46
     *
     */
    public class TestMain implements JohnsonEventListener {
    
    	public void onAction(JohnsonEventObject eventObject) {
    		JohnsonEventSource source = (JohnsonEventSource) eventObject.getSource();
    		source.sourceFunction("事件监听器  监听到事件--事件类型: " + String.valueOf(eventObject.getEventType()));
    	}
    
    	public static void main(String[] args) {
    		JohnsonEventSource source = new JohnsonEventSource();
    		source.addEventListener(new TestMain());
    		source.event1();
    		source.event2();
    		source.event3();
    	}
    
    }
    

    2.外部类作为事件监听器 :

    监听器:

    package xuyihao;
    
    import event.JohnsonEventListener;
    import event.JohnsonEventObject;
    import event.JohnsonEventSource;
    
    /**
     * 外部类作为事件监听器的外部类
     * 
     * @Author Xuyh created at 2016年12月11日 下午6:02:46
     *
     */
    public class OuterListerner implements JohnsonEventListener {
    
    	public void onAction(JohnsonEventObject eventObject) {
    		JohnsonEventSource source = (JohnsonEventSource) eventObject.getSource();
    		source.sourceFunction("事件监听器  监听到事件--事件类型: " + String.valueOf(eventObject.getEventType()));
    	}
    }
    
    
    /**
     * 外部类作为事件监听器的外部类
     * 
     * @Author Xuyh created at 2016年12月11日 下午6:02:46
     *
     */
    public class JohnsonMain {
    	public static void main(String args[]) {
    		exentTest();
    	}
    
    	/**
    	 * 外部类作为事件监听器
    	 */
    	public static void exentTest() {
    		JohnsonEventSource source = new JohnsonEventSource();
    		source.addEventListener(new OuterListerner());
    		source.event1();
    		source.event2();
    		source.event3();
    	}
    }

    /**
     * 
     * 
     * @Author Xuyh created at 2016年12月11日 下午6:02:46
     *
     */
    public class JohnsonMain {
    	public static void main(String args[]) {
    		exentTest();
    	}
    
    	/**
    	 * 外部类作为事件监听器
    	 */
    	public static void exentTest() {
    		JohnsonEventSource source = new JohnsonEventSource();
    		source.addEventListener(new OuterListerner());
    		source.event1();
    		source.event2();
    		source.event3();
    	}
    }


    3.匿名内部类作为事件监听器 :

    import event.JohnsonEventListener;
    import event.JohnsonEventObject;
    import event.JohnsonEventSource;
    
    public class JohnsonMain {
    	public static void main(String args[]) {
    		eventTest();
    	}
    		
    
    	/**
    	 * 匿名方式实现监听
    	 */
    	public static void eventTest() {
    		System.out.println("匿名方式实现监听");
    		// 定义事件源
    		JohnsonEventSource source = new JohnsonEventSource();
    		// 事件源绑定事件监听1,这里使用匿名方式实现监听器
    		source.addEventListener(new JohnsonEventListener() {
    			public void onAction(JohnsonEventObject eventObject) {
    				JohnsonEventSource source1 = (JohnsonEventSource) eventObject.getSource();
    				source1.sourceFunction("事件监听器 1 监听到事件--事件类型: " + String.valueOf(eventObject.getEventType()));
    				// 这里也可以直接使用外部的source对象,本质上是一个对象
    			}
    		});
    		// 事件源绑定事件监听2
    		source.addEventListener(new JohnsonEventListener() {
    			public void onAction(JohnsonEventObject eventObject) {
    				JohnsonEventSource source2 = (JohnsonEventSource) eventObject.getSource();
    				source2.sourceFunction("事件监听器 2 监听到事件--事件类型: " + String.valueOf(eventObject.getEventType()));
    			}
    		});
    		// 启动事件
    		source.event1();
    		source.event2();
    		source.event3();
    	}
    }

    4.内部类作为事件监听器:

    import event.JohnsonEventListener;
    import event.JohnsonEventObject;
    import event.JohnsonEventSource;
    
    /**
     * 内部类作为事件监听器
     *
     * Created by Xuyh at 2016/08/05 下午 03:26.
     */
    public class JohnsonMain {
    	public static void main(String args[]) {
    		eventTest();
    	}
    
    	/**
    	 * 定义监听器类实现接口方式
    	 */
    	public static void eventTest() {
    		System.out.println("定义监听器类实现接口方式实现监听");
    		JohnsonEventSource source = new JohnsonEventSource();
    		// 事件源绑定事件监听1
    		source.addEventListener(new ExampleListener(1));
    		// 事件源绑定事件监听2
    		source.addEventListener(new ExampleListener(2));
    		// 启动事件
    		source.event1();
    		source.event2();
    		source.event3();
    	}
    
    	public static class ExampleListener implements JohnsonEventListener {
    		private int number;
    
    		public ExampleListener(int number) {
    			this.number = number;
    		}
    
    		public void onAction(JohnsonEventObject eventObject) {
    			JohnsonEventSource source = (JohnsonEventSource) eventObject.getSource();
    			source.sourceFunction("事件监听器 " + number + " 监听到事件--事件类型: " + String.valueOf(eventObject.getEventType()));
    		}
    	}
    }


    展开全文
  • 2个目标文件 摘要:Java源码,网络相关,UDP 基于JAVA的UDP服务器模型源代码,内含UDP服务器端模型和UDP客户端模型两个小程序,向JAVA初学者演示UDP C/S结构的原理。 简单聊天软件CS模式 2个目标文件 一个简单的CS模式...
  • ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();
  • 现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动 而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化的内容...
        现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动
    
    而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些

    固定的对象等等。首先来看一下ServletContextListener接口的源代码:

    public abstract interface ServletContextListener extends EventListener{
        public abstract void contextInitialized(ServletContextEvent paramServletContextEvent);
        public abstract void contextDestroyed(ServletContextEvent paramServletContextEvent);
    }

    下面利用监听器对数据库连接池DataSource的初始化演示它的使用:ListenerTest.java
    import javax.servlet.ServletContext;   
    import javax.servlet.ServletContextEvent;   
    import javax.servlet.ServletContextListener;   
    import org.apache.commons.dbcp.BasicDataSource;       
    /**
     * 现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的
     * 服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化
     * 的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。
     * 
     * 示例代码:使用监听器对数据库连接池DataSource进行初始化
     */ 
    public class ListenerTest implements ServletContextListener{     
       // 应用监听器的销毁方法   
       public void contextDestroyed(ServletContextEvent servletContextEvent) {   
            ServletContext servletContext = servletContextEvent.getServletContext();
            // 在整个web应用销毁之前调用,将所有应用空间所设置的内容清空
            servletContext.removeAttribute("dataSource");
            System.out.println("销毁工作完成...");  
       }   
        // 应用监听器的初始化方法   
        public void contextInitialized(ServletContextEvent servletContextEvent) {   
            // 通过这个事件可以获取整个应用的空间   
            // 在整个web应用下面启动的时候做一些初始化的内容添加工作   
            ServletContext servletContext = servletContextEvent.getServletContext();  
            // 设置一些基本的内容;比如一些参数或者是一些固定的对象   
            // 创建DataSource对象,连接池技术 dbcp   
            BasicDataSource basicDataSource = new BasicDataSource(); 
            basicDataSource.setDriverClassName("com.jdbc.Driver"); 
            basicDataSource.setUrl("jdbc:mysqlocalhost:3306/"); 
            basicDataSource.setUsername("root");   
            basicDataSource.setPassword("root");   
            basicDataSource.setMaxActive(10);//最大连接数   
            basicDataSource.setMaxIdle(5);//最大管理数   
            //bds.setMaxWait(maxWait); 最大等待时间   
            // 把 DataSource 放入ServletContext空间中,   
            // 供整个web应用的使用(获取数据库连接)
            servletContext.setAttribute("dataSource", basicDataSource);   
            System.out.println("应用监听器初始化工作完成...");   
            System.out.println("已经创建DataSource...");  
        }   
    }

    web.xml中配置如下,很简单:

        <!-- 配置应用监听器  -->   
        <listener>   
            <listener-class>com.ycq.ListenerTest</listener-class>   
        </listener>  

    这样配置好了之后,以后在web应用中就可以通过ServletContext取得BasicDataSource对象,从而获取与数据库的连接,提高性能,方便使用。

    示例代码二:

    import java.io.File;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import com.i2f.fsp.deploy.TransactionDeployer;
    /**
     * 监听器随着项目的启动而启动
     *
     */
    public class ListenerTest2 implements ServletContextListener{
        // 销毁监听器 
        public void contextDestroyed(ServletContextEvent servletContextEvent) {
            System.out.println("date20161020095500 :" + servletContextEvent.getServletContext());
        }
        public void contextInitialized(ServletContextEvent servletContextEvent) {
            try{
                // 获取项目跟路径
                String basePath = servletContextEvent.getServletContext().getRealPath("/");
                // D:\apache-tomcat-6.0.41\webapps\i2money\ 绝对路径
                System.out.println("basePath20161020094700 :" + basePath);
                if (!(basePath.endsWith(File.separator))){
                    basePath = basePath + File.separator;
                }
                basePath = basePath + "WEB-INF" + File.separator + "classes" + File.separator;
                new TransactionDeployer(basePath).deploy();
                // D:\apache-tomcat-6.0.41\webapps\i2money\WEB-INF\classes\
                System.out.println("basePath20161020094701 :" + basePath);
            }
            catch (Exception e){
                e.printStackTrace();
                System.exit(-1);
            }
        }
    }

    示例代码三:

    import javax.servlet.http.HttpSession;
    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    public class UserLogoutListener implements HttpSessionListener{
        protected final Log log = LogFactory.getLog(super.getClass());
        public void sessionCreated(HttpSessionEvent event){
            this.log.error("session created. id = " + event.getSession().getId());
        }
        public void sessionDestroyed(HttpSessionEvent event){
            this.log.error("session destroyed.id = " + event.getSession().getId());
            HttpSession session = event.getSession();
            ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(session.getServletContext());
            OnlineUserMonitorClient client = (OnlineUserMonitorClient)context.getBean("onlineUserMonitorClient");
            client.afterSessionDestroyed(session);
        }
    }

    监听器在实际项目中的应用,监听器在java web中应用的较多,比如:统计当前在线人数、自定义session扫描器。
    --------------------- 应用一:统计当前在线人数 ---------------------

    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    /**
     * @description HttpSessionListener监听器实现统计网站在线人数的功能
     */
    public class SessionListener implements HttpSessionListener{
    
        public static int TOTAL_ONLINE_USERS = 0;
        public void sessionCreated(HttpSessionEvent httpSessionEvent) {
            ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
            TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");
            // 如果用户退出,TOTAL_ONLINE_USERS自减1
            if(TOTAL_ONLINE_USERS == 0){
                servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);
            }
            else{
                TOTAL_ONLINE_USERS--;
                servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);
            }
        }
    
        public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
            ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
            TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");
            // 如果用户登录,TOTAL_ONLINE_USERS自增1
            if(TOTAL_ONLINE_USERS == 0){
                servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);
            }
            else{
                TOTAL_ONLINE_USERS++;
                servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);
            }
        }
    }

    --------------------- 应用二:自定义session扫描器 ---------------------

    import java.util.LinkedList;
    import java.util.List;
    import java.util.Timer;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    import jeus.util.concurrent50.Collections;
    /**
     * @description 当网站用户量增加时,session占用的内存会越来越大,这时session的管理,将会是一项很大的
     * 系统开销,为了高效的管理session,我们可以写一个监听器,定期清理掉过期的session
     */
    public class SessionScanerListener implements HttpSessionListener,ServletContextListener{
        // 创建一个线程安全的集合,用来存储session
        @SuppressWarnings("unchecked")
        List<HttpSession> sessionList = Collections.synchronizedList(new LinkedList<HttpSession>());
        private Object lock = new Object();
        
        public void sessionCreated(HttpSessionEvent httpSessionEvent) {
            System.out.println("session 创建成功...");
            HttpSession httpSession = httpSessionEvent.getSession();
            synchronized (lock){
                sessionList.add(httpSession);
            }
        }
    
        public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
            System.out.println("session 销毁成功...");
        }
        // web应用关闭时触发contextDestroyed事件
        public void contextDestroyed(ServletContextEvent servletContextEvent) {
            System.out.println("web应用关闭...");
        }
    
        // web应用启动时触发contextInitialized事件
        public void contextInitialized(ServletContextEvent servletContextEvent) {
            System.out.println("web应用初始化...");
            // 创建定时器
            Timer timer = new Timer();
            // 每隔30秒就定时执行任务
            timer.schedule(new MyTask(sessionList,lock), 0, 1000*30);
        }
    }

    import java.util.List;
    import java.util.ListIterator;
    import java.util.TimerTask;
    import javax.servlet.http.HttpSession;
    /**
     * 定时器,定义定时任务的具体内容
     */
    public class MyTask extends TimerTask{
        private List<HttpSession> list;
        // 存储传递过来的锁
        private Object lock;
        // 构造方法
        MyTask(List<HttpSession> list, Object lock){
            this.list = list;
            this.lock = lock;
        }
        @Override
        public void run() {
            // 考虑到多线程的情况,这里必须要同步
            synchronized (lock){
                System.out.println("定时器开始执行...");
                ListIterator<HttpSession> listIterator = list.listIterator();
                while(listIterator.hasNext()){
                    HttpSession httpSession = listIterator.next();
                    // httpSession.getLastAccessedTime() = session的最后访问时间
                    if(System.currentTimeMillis() - httpSession.getLastAccessedTime() > 1000*30){
                        // 手动销毁session
                        httpSession.invalidate();
                        // 从集合中移除已经被销毁的session
                        listIterator.remove();
                    }
                }
            }
        }
    }


    展开全文
  • java基础---volatile底层实现原理详解

    千次阅读 2020-04-01 01:15:07
    JMM—java内存模型 想知道volatile实现原理首先得去了下解JMM,我们都知道JVM会为每一个thread开辟一块自己的工作空间,在我们操作变量时是从主内存拿到变量的一个副本,然后对副本进行操作后再刷新到主内存中这么一...

    大家都知道生产中可以使用volatile达到保证可见性和指令重排的目的。但是对其实现原理并不是很清楚,为了加深学习和理解感觉很有必要来写篇博客总结一下。

    JMM—java内存模型

    想知道volatile实现原理首先得去了下解JMM,我们都知道JVM会为每一个thread开辟一块自己的工作空间,在我们操作变量时是从主内存拿到变量的一个副本,然后对副本进行操作后再刷新到主内存中这么一个总体的流程。
    在这里插入图片描述
    先简单来看一下如果要改变一个变量值需要经过哪些操作:
    1.首先会执行一个read操作将主内存中的值读取出来
    2.执行load操作将值副本写入到工作内存中
    3.当前线程执行user操作将工作内存中的值拿出在经过执行引擎运算
    4.将运算后的值进行assign操作写会到工作内存。
    5.线程将当前工作内存中新的值存储回主内存,注意只是此处还没有写入到主内存的共享变量,主内存中的值还没有改变。
    6.最后一步执行write操作将主内存中的值刷新到共享变量,到此处一个简单的流程就走完了。

    下图的8种操作是定义在java内存模型当中的,我们的任何操作都需要通过这几种方式来进行。
    在这里插入图片描述
    简单看了一下操作流程后继续回到volatile关键字,在多个个线程工作内存看起来互无关联的情况下是怎么做到保证变量的可见性的?

    这里我们不得不先去了解一个名词:总线 ------什么是总线?它是干什么的?

    度娘给出的解释: 由于总线是连接各个部件的一组信号线。通过信号线上的信号表示信息,通过约定不同信号的先后次序即可约定操作如何实现。简单来说就是我们的cpu和内存进行交互就得通过总线,它们不能隔空产生连接。总线就是一条共享的通信链路,它用一套线路来连接多个子系统。

    总线按功能和规范可分为五大类型:

    • 数据总线(Data Bus):在CPU与RAM之间来回传送需要处理或是需要储存的数据。
    • 地址总线(Address Bus):用来指定在RAM(Random Access Memory)之中储存的数据的地址。
    • 控制总线(Control Bus):将微处理器控制单元(Control Unit)的信号,传送到周边设备。
    • 扩展总线(Expansion Bus):外部设备和计算机主机进行数据通信的总线,例如ISA总线,PCI总线。
    • 局部总线(Local Bus):取代更高速数据传输的扩展总线。

    最初实现就是通过总线加锁的方式也就是上面的lock与unlock操作,但是这种方式存在很大的弊端。会将我们的并行转换为串行,从而失去了多线程的意义。这里不详细展开了解一下即可。下面才是我们真正需要认识的

    MESI缓存一致性协议:

    CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算需要高速的数据。为了解决这个问题,CPU厂商在CPU中内置了少量的高速缓存以解决I\O速度和CPU运算速度之间的不匹配问题。为了解决这个问题CPU厂商采用了缓存的解决方案,知道目前我们正在使用的多级的缓存结构。我们可以到任务管理器看一下:
    在这里插入图片描述
    目前流行的多级缓存结构:
    在这里插入图片描述
    多核CPU的情况下有多个一级缓存,如何保证缓存内部数据的一致,不让系统数据混乱。这里就引出了一个一致性的协议MESI。这里我们大概只需要有这么一个概念就可以。而当我们共享变量用volatile修饰后就会帮我们在总线开启一个MESI缓存协议。同时会开启CPU总线嗅探机制(监听),当其中一个线程将修改后的值传输到总线时,就会触发总线的监控告诉其他正在使用当前共享变量的线程使当前工作内存中的值失效。这个时候工作空间中的副本变量就没有了,从而需要重新去获取新的值。

    底层实现主要是通过汇编Lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并写回到主内存。总的来说就是Lock指令会将当前处理器缓存行数据立即写回到系统内存从而保证多线程数据缓存的时效性。这个写回内存的操作同时会引起在其它CPU里缓存了该内存地址的数据失效(MESI协议)。

    为了保证在从工作内存刷新回主内存这个阶段主内存数据的安全性,在store前会使用内存模型当中的lock操作来锁定当前主内存中的共享变量。当主内存变量在write操作后才会将当前lock释放掉,别的线程才能继续进来获取新的值。

    查看Java的汇编指令: 想要实际去看下底层的汇编指令,需要在jre/bin目录下添加额外的两个包
    下载链接:百度网盘链接,提取码:d753

    在这里插入图片描述
    然后配置idea,在 VM options 选项中输入:-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*类名.方法名或者-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly就可以在控制台看到输出的lock汇编指令。
    在这里插入图片描述
    我们都知道volatile并不能保证原子性,到这里也可以解释通为什么了。假设当前有两个线程同时对共享变量进行+1运算,Thread1比Thread2先进行了Lock操作拿到了锁,此时由于我们的总线嗅探机制Thread2就会知道共享变量值已经修改过了,从而导致当前Thread2工作内存中的副本变量失效。只能再次去主内存中取新的值,但这样无形之中Thread2就已经浪费掉了一次操作机会。从而导致最终结果小于预期的情况出现。(比如最常用到的那种两个线程同时对一个volatile修饰的int进行加减运算的例子)

    提示:
    如 long a = 100L long b = a+1
    在这里a+1并不是我们想象中的原子操作因为long在java中占8个子节一个64位写操作实际上将会被拆分为2个32位的操作,这一行为的直接后果将会导致最终的结果是不确定的并且缺少原子性的保证。
    在Java虚拟机规范中同样也有类似的描述:“For the purposesof the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each32-bit half. This can result in a situation where a thread sees the first 32 bitsof a 64-bit value from one write, and the second 32 bits from anotherwrite.”

    翻译:对于Java编程语言内存模型来说,对非易失性长值或双值的一次写操作被视为两次单独的写操作:一次写32位的一半。这可能导致这样一种情况,一个线程看到一个64位值的前32位从一个写,和第二个32位从另一个写。

    官网地址

    指令重排

    在之前很经典的单例设计模式中为了防止DCL在指令重排后导致线程不安全的情况,就使用了volatile来防止指令重排。

    我们知道为了提高程序执行的性能,编译器和执行器(处理器)通常会对指令做一些优化(重排序)。volatile通过内存屏障实现了防止指令重排的目的。同时lock前缀指令相当于一个内存屏障,它会告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。

    不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。
    Java内存屏障主要有Load和Store两类:

    • 对Load Barrier来说,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据
    • 对Store Barrier来说,在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存
      为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。然而,对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略:
    • 在每个volatile写操作的前面插入一个StoreStore屏障。
    • 在每个volatile写操作的后面插入一个StoreLoad屏障。
    • 在每个volatile读操作的后面插入一个LoadLoad屏障。
    • 在每个volatile读操作的后面插入一个LoadStore屏障。
    展开全文
  • java集合底层原理面试相关二

    千次阅读 2018-09-04 21:54:36
    2、HashMap的源码,实现原理底层结构。 HashMap的实现原理:首先有一个每个元素都是链表(可能表述不准确)的数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,...
  • JAVA上百实例源码以及开源项目

    千次下载 热门讨论 2016-01-03 17:37:40
     基于JAVA的UDP服务器模型源代码,内含UDP服务器端模型和UDP客户端模型两个小程序,向JAVA初学者演示UDP C/S结构的原理。 简单聊天软件CS模式 2个目标文件 一个简单的CS模式的聊天软件,用socket实现,比较简单。 ...
  • JAVA框架

    千次阅读 2019-10-15 10:42:43
    JAVA框架SpringSpringMVCSpringBootDubboMavenRedisMybatis Spring SpringMVC SpringBoot Dubbo Maven Redis Mybatis
  • 前面我们基于实际案例搭建了缓存高...今天我们就来看看redis sentinel即哨兵机制的相关底层原理以及我们在生产中需要避的坑。 什么是redis sentinel 哨兵在redis集群架构中是一个非常重要的组件,其主要功能有下面...
  • java nio及操作系统底层原理

    万次阅读 多人点赞 2017-06-27 12:53:20
    版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+] ...相关资料IO基本概念 ...阻塞IO模型非阻塞IO模型IO复用模型信号驱动异步IO模型异步IO模型总结 ...Java对BIONIOAIO的支持AIORefer
  • JAVA面试笔记

    千次阅读 多人点赞 2019-03-07 17:52:40
    JAVA面试笔记Java基础面试1、HashMap源码?5、Set的实现?6、讲解线程execute?8、讲解Runable和Callnable的区别?9、使用泛型的好处?10、JDK动态代理和Cglib的区别?Spring面试题Spirng基础面试1、什么是Spring?2...
  • Java调试原理初探

    2019-11-14 22:17:17
    对于所有程序员,程序调试是一项必备的技能。在java程序中,最简单的就是通过System.out.println...对于这些调试器是如何实现的,这就需要了解本文的重点——JPDA(Java Platform Debugger Architecture)Java平台调...
  • 文章目录1、类加载和实例化2、Java是值传递还是引用传递3、类加载的主要过程4、什么是GC5、简述垃圾回收过程6、内存泄漏7、导致内存泄漏的场景8、Java中堆和栈的区别9、ArrayList、LinkedList、Vector的区别10、...
  • Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。...
  • 它既是一门非常简单的语言,又是一门及其复杂的语言,要想真正精通JavaScript,我们就必须深入的去了解它的一些底层设计原理。本文将参考《JavaScript高级程序设计》和《你不知道的JS》系列丛书,为大家讲解一些关于...
  • 深入理解Java8中Stream的实现原理

    万次阅读 多人点赞 2019-02-25 15:40:26
    二.Stream的实现原理 1.一种直白的实现方式 2.Stream流水线解决方案 1).操作如何记录 2).操作如何叠加 3).叠加之后的操作如何执行 4).执行后的结果在哪里 一.容器执行Lambda表达式的方式 1.回顾 首先回顾...
  • 彻底搞懂 IO 底层原理

    千次阅读 2020-11-30 10:19:11
    武侠小说里有很多的“心法”和“招式”。计算机技术里的“心法”和“招式”...并且在它之上衍生出了语言层面用于实战的技术,比如我们熟悉的java语言中的NIO或者像Netty这样的框架。 一、混乱的 IO 概念 IO是Input...
  • springboot redis key失效监听机制

    千次阅读 2019-01-24 16:58:03
    reids2.8之后支持的失效监听 因为 Redis 目前的订阅与发布功能采取的是发送即忘(fire and forget)策略, 所以如果你的程序需要可靠事件通知(reliable notification of events), 那么目前的键空间通知可能并不...
  • 然后我决定,自己制作一个简单的事件监听器! 当我们需要用一个按钮的单击去触发两个方法的时候,会发现前一个总会被第二个方法的结果覆盖掉,我现在定义了一个id为“btn”的按钮: btn = document.getElementById(...
  • 观察者模式在前端运用的很多,很多的框架rxjava,rxswift,rac 等都运用了观察者,这里我一直都是使用者但是并没有怎么深入的去研究实现的原理,经过学习,加上自己的理解,来接下笔记。 观察者模式的基本概念很...
  • 深入了解NIO底层原理

    千次阅读 2020-07-04 15:55:16
    Redis底层采用NIO中的多路IO复用的机制,很好地支持高并发,并且能实现线程安全。 Redis官方没有windows版本,只有Linux版本。 NIO在不同的操作系统上实现的方式有所不同,在Windows操作系统使用select实现轮训,...
  • 拜托!面试请不要再问我Spring Cloud底层原理

    万次阅读 多人点赞 2018-11-07 08:35:18
    以上就是我们通过一个电商业务场景,阐述了Spring Cloud微服务架构几个核心组件的底层原理。   文字总结还不够直观?没问题! 我们将Spring Cloud的5个核心组件通过一张图串联起来,再来直观的感受一下其...
  • 底层原理分析Nginx为什么这么快

    千次阅读 2020-06-02 14:53:46
    数据复制:用户空间和内核空间,复制连接就绪状态信息 epoll: event 事件驱动 事件机制:避免线性扫描 为每个 fd,注册一个监听事件 fd 变更为就绪时,将 fd 添加到就绪链表 fd 数量:无限制(OS 级别的限制,单个...
  • 拜托!面试请不要再问我Spring Cloud底层原理

    万次阅读 多人点赞 2019-02-27 14:53:23
    以上就是我们通过一个电商业务场景,阐述了Spring Cloud微服务架构几个核心组件的底层原理。   文字总结还不够直观?没问题! 我们将Spring Cloud的5个核心组件通过一张图串联起来,再来直观的感受一下其...
  • Java JVMTI和Instrumention机制介绍

    千次阅读 2019-03-09 16:17:35
    文章目录1、JVMTI 介绍1.1 Agent的工作形式1.2 JDPA 相关介绍2、Instrumention 机制2.1 Instrumention支持的功能2.2 基于Instrumention开发一个Agent2.2.1 编写premain函数2.2.2 打成jar包2.2.3 编写测试类2.3 如何...
  • HDFS是Hadoop的一大核心,关于HDFS需要掌握的有:分布式系统与HDFS、HDFS的体系架构和基本概念、HDFS的shell操作、Java接口以及常用的API、Hadoop的RPC机制、远程debugDistributed File System数据量越来越多,在一...
  • Java NIO原理图文分析及代码实现  前言:  最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。可以...
  • 测试开发笔记

    万次阅读 多人点赞 2019-11-14 17:11:58
    测试开发笔记 第一章 测试基础 7 什么是软件测试: 7 ★软件测试的目的、意义:(怎么做好软件测试) 7 3.软件生命周期: 7 第二章 测试过程 8 1.测试模型 8 H模型: 8 V模型 9 2.内部测试 10 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,705
精华内容 10,682
关键字:

java监听机制底层原理

java 订阅