1.线程的基础和线程的共享和协作

1.0 基础概念

线程 vs 进程

        进程:程序运行时,资源分配的最小单位。进程内会有一个或者多个线程,线程间共享这个进程的资源。
        线程:cpu调度的最小单位,依赖进程而存在。

并发 vs 并行

        并发:单位时间内,可以处理事情的能力。
        并行:同一时刻,可以同时处理事情的能力。

并发编程的好处和注意事项

        好处:充分利用了cpu的资源。加快用户的响应时间,程序的模块化和异步化。
        注意事项:
        + 并发会存在死锁的情况
        + 并发共享资源,会存在冲突的情况。
        + 过多的线程会导致机器宕机。

守护线程

    随着主线程结束而结束的线程。例如GC线程。特别地,如果一个线程被设置为守护线程,那么线程内容的finally代码不一定总被执行。

创建线程的几种方式

        + 继承Thread
        + 实现Runnalbe
        + 实现Callable + FutureTask

停止线程的几种方式

stop resume

        这2个方法是强制中断吸纳线程,已经被废弃的方法。

interrupt()、 isInterrupted() 、static interrupted()

        + interrupt()方法是告诉线程需要中断一些,但是是否会中断由线程自身决定。所以一般结合isInterrupted() 、static interrupted()2个方法一起使用。
        + isInterrupted() 返回当前线程的中断状态,不会重置线程的中断状态。
        + static interrupted() 返回当前线程的中断状态,并且会重置isInterrupted()为false
        在sleep(),wait()等方法都会抛出一个InterruptedException,这时候捕获这个异常的时候,要主动重新调用interrupt()方法。否则可能无法正确中断线程。

线程的状态

        此处应该有一个图

1.2 线程之间的共享

synchronized内置锁

volatile 关键字

    > 

线程私有变量 ThreadLocal

    + get() 获取每个线程自己的threadLocals中的本地变量副本。
    + set() 设置每个线程自己的threadLocals中的线程本地变量副本。
    ThreadLocal有一个内部类ThreadLocalMap:
                    public T get() {
                    Thread t = Thread.currentThread();
                    //根据当前的线程返回一个ThreadLocalMap.点进去getMap
                    ThreadLocalMap map = getMap(t);
                    if (map != null) {
                        ThreadLocalMap.Entry e = map.getEntry(this);
                        if (e != null) {
                            @SuppressWarnings("unchecked")
                            T result = (T)e.value;
                            return result;
                        }
                    }
                    return setInitialValue();
                }
                
                 //点击去getMap(t)方法发现其实返回的是当前线程t的一个内部变量ThreadLocal.ThreadLocalMap
                    ThreadLocalMap getMap(Thread t) {
                    return t.threadLocals;
                }
                //由此可以知道,当调用ThreadLocal的get方法是,其实返回的是当前线程的threadLocals(类型是ThreadLocal.ThreadLocalMap)中的变量。调用set方法也类似。
                
                //举例一个使用场景
                /**
             * ThreadLocal使用场景:把数据库连接对象存放在ThreadLocal当中.
             * 优点:减少了每次获取Connection需要创建Connection
             * 缺点:因为每个线程本地会存放一份变量,需要考虑内存的消耗问题。
             * @author luke Lin
             *
             */
            public class ConnectionThreadLocal {
                private final static String DB_URL = "jdbc:mysql://localhost:3306:test";
                private static ThreadLocal<Connection> connectionHolder  = new ThreadLocal<Connection>(){
                    protected Connection initialValue() {
                        try {
                            return DriverManager.getConnection(DB_URL);
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                        return null;
                    };
                };
                
                /**
                 * 获取连接
                 * @return
                 */
                public Connection getConnection(){
                    return connectionHolder.get();
                }
                
                /**
                 * 释放连接
                 */
                public void releaseConnection(){
                    connectionHolder.remove();
                }
            }
                
                //解决ThreadLocal中弱引用导致内存泄露的问题的建议
                + 声明ThreadLoal时,使用private static修饰
                + 线程中如果本地变量不再使用,即使使用remove()

1.3 线程间的协作

wait() notify() notifyAll()

        //1.3.1通知等候唤醒模式
            //1)等候方
                获取对象的锁
                在循环中判断是否满足条件,如果不满足条件,执行wait,阻塞等待。
                如果满足条件跳出循环,执行自己的业务代码
            
            //2)通知方
                获取对象的锁
                更改条件
                执行notifyAll通知等等待方
        //1.3.2 
            //wait notify notifyAll都是对象内置的方法
            //wait notify notifyAll 都需要加synchronized内被执行,否则会抱错。
            //执行wait方法是,会让出对象持有的锁,直到以下2个情况发生:1。被notify/notifyAll唤醒。2。wait超时
        //1.3.3 举例使用wait(int millis),notifyAll实现一个简单的线城池超时连接
/*
 * 连接池,支持连接超时。
 * 当连接超过一定时间后,做超时处理。
 */
public class DBPool2 {
    LinkedList<Connection> pools;
    //初始化一个指定大小的新城池
    public DBPool2 (int poolSize) {
        if(poolSize > 0){
            pools =  new LinkedList<Connection>(); 
            for(int i=0;i < poolSize; i++){
                pools.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }
    
    
    /**
     * 获取连接
     * @param remain 等待超时时间
     * @return
     * @throws InterruptedException 
     */
    public Connection fetchConn(long millis) throws InterruptedException {
        // 超时时间必须大于0,否则抛一场
        synchronized (pools) {
            if (millis<0) {
                while(pools.isEmpty()) {
                    pools.wait();
                }
                return pools.removeFirst();
            }else {
                // 超时时间
                long timeout = System.currentTimeMillis() + millis;
                long remain = millis;
                // 如果当前pools的连接为空,则等待timeout,如果timeout时间还没有返回,则返回null。
                while (pools.isEmpty() && remain > 0) {
                    try {
                        pools.wait(remain);
                        remain = timeout - System.currentTimeMillis();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Connection result = null;
                if (!pools.isEmpty()) {
                    result = pools.removeFirst();
                }
                return result;
            }
        }

    }
    
    /**
     * 释放连接
     */
    public void releaseConn(Connection con){
        if(null != con){
            synchronized (pools) {
                pools.addLast(con);
                pools.notifyAll();
            }
        }
    }
}

sleep() yield()

join()

        面试点:线程A执行了县城B的join方法,那么线程A必须等到线程B执行以后,线程A才会继续自己的工作。

wait() notify() yield() sleep()对锁的影响

        面试点:
        线程执行yield(),线程让出cpu执行时间,和其他线程同时竞争cup执行机会,但如果持有的锁不释放。
        线程执行sleep(),线程让出cpu执行时间,在sleep()醒来前都不竞争cpu执行时间,但如果持有的锁不释放。
        notify调用前必须持有锁,调用notify方法本身不会释放锁。
        wait()方法调用前必须持有锁,调用了wait方法之后,锁就会被释放。当wait方法返回的时候,线程会重新持有锁。

posted on 2018-12-28 09:25  lukelin1989  阅读(...)  评论(... 编辑 收藏

导航