Java线程解析

定义

线程是现代操作系统调度的最小单元,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
处理器在这些线程上高速切换,让使用者感觉这些线程在同时执行。

1、优先级

线程可以通过一个整型变量priority来控制优先级,范围从 1~10,默认为5,优先级高的线程分配的时间片的数量要多于优先级低的线程,但很多操作系统忽略掉了Java的线程优先级,所以基本上用不到。

2、状态

状态名称 说明
NEW 初始状态,线程被构建,但是没有调用start()方法
RUNNABLE 运行状态,java线程将操作系统中的就绪和运行两种状态并称为“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,改状态表示当前线程需要等待其他线程做出一些特定动作
TIME_WAITING 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕

3、中断

中断可以理解为线程的一个标识位属性,它标识一个运行中的线程是否被其他线程进行了中断操作
其他线程调用该线程的interrupt()方法中断该线程,此时通过isInterrupted()方法来判断该线程的中断状态时,返回的是true,若通过静态方法Thread.interrupted()方法对当前线程的中断标识位进行复位,则isInterrupted()方法返回的是false
同时,对于睡眠状态的线程执行interrupt()方法,中断标志位不会被设置,只有正在执行的线程,被中断时才会设置。

4、等待/通知

方法名称 描述
notify() 通知一个在对象上等待的线程,使其从wait()方法上返回,前提是该线程获取到了对象的锁
notifyAll() 通知所有等待在对象上的线程
wait() 调用该方法的线程会进入WAITING状态,只有等待另外线程的通知或被中断才会返回,调用此方法后,会释放对象的锁
wait(long) 超时等待一段时间,这里的参数是毫秒,如果没有通知就返回
wait(long,int) 对于超时时间更细粒度的控制

涉及到的API:

方法名称 描述
notify() 通知一个在对象上等待的线程,使其从wait()方法上返回,前提是该线程获取到了对象的锁
notifyAll() 通知所有等待在对象上的线程
wait() 调用该方法的线程会进入WAITING状态,只有等待另外线程的通知或被中断才会返回,调用此方法后,会释放对象的锁
wait(long) 超时等待一段时间,这里的参数是毫秒,如果没有通知就返回
wait(long,int) 对于超时时间更细粒度的控制

下面通过一段代码 ,来了解加等待\唤醒机制。

public class WaitNotify {

    static boolean flag = true;
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Runnable wait = () -> {
            synchronized (lock) {
                while (flag) {
                    System.out.println(Thread.currentThread() + " flag is true. wait @ " + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME) );
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread() + " flag is false. running    @ "+   LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
            }
        };
        Runnable notify = () -> {
          synchronized (lock) {
              System.out.println(Thread.currentThread() + "hold lock. notify @ " + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
              lock.notifyAll();
              flag = false;
              try {
                  TimeUnit.SECONDS.sleep(5);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          synchronized (lock) {
              System.out.println(Thread.currentThread() + " hold lock again. sleep @ " +  LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
              try {
                  TimeUnit.SECONDS.sleep(5);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
        };
        Thread waitThread = new Thread(wait, "WaitThread" );
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        Thread notifyThread = new Thread(notify, "NotifyThread" );
        notifyThread.start();
    }
}

代码中定义了一个锁对象,一个状态标识,具体的执行过程如下:

  1. waitThread线程获取到lock对象的锁,然后执行wait()方法,并释放锁;
  2. notifyThread线程获取到lock对象的锁,然后执行notifyAll()方法,此时由于还没释放锁,所以waitThread并不会继续执行,当同步代码块执行结束后,notifyThread的第二个同步代码块与waitThread继续“争抢”lock对象的锁;
  3. 抢到锁的对象继续执行,整个程序执行完毕。

按照以上内容,会输出如下日志,其中第三、四条由于锁争抢的原因,位置可能调换:

Thread[WaitThread,5,main] flag is true. wait @ 2017-12-07T20:28:38.962
Thread[NotifyThread,5,main]hold lock. notify @ 2017-12-07T20:28:39.912
Thread[WaitThread,5,main] flag is false. running    @ 2017-12-07T20:28:44.913
Thread[NotifyThread,5,main] hold lock again. sleep @ 2017-12-07T20:28:45.913

通知唤醒机制的经典使用场景是消费者与生产者。
当消费者获取到锁,并判断条件不满足时,便调用锁的wait()方法,当生产者改变了条件时,就通过notifyAll来进行通知。
消费者遵循如下原则:

  1. 获取对象的锁;
  2. 如果条件不满足,则调用wait()方法;
  3. 如果条件满足,则执行对应的逻辑。

生产者遵循如下逻辑:

  1. 获取对象的锁;
  2. 改变条件;
  3. 通知所有等待在对象上的线程。

还可以将wait()方法替换为wait(long)方法,增加超时控制,以免造成线程永久阻塞调用者的情况。

5、synchronized与volatile

最简单的区分
synchronized:原子性,线程安全
volatile:可见性,不保证原子性,线程不安全
所以,如果有数据仅要求多线程共享,而不涉及原子性改动时,可以考虑添加volatile关键字,但并不建议大量添加,以免影响性能。

示例

通过以上内容,应该对Java线程有了基本的了解,下面通过超时等待\唤醒机制来编写一个建议的数据库连接池。
思路:

  1. 能定义连接线程池容量;
  2. 可以获取和释放连接;
  3. 具备超时机制,以免永久阻塞。

线程池代码:

public class ConnectionPool {
    private LinkedList<Connection> pool  = new LinkedList<>();;

    //创建时设定容量
    public ConnectionPool(int initialSize){
        for(int i=0;i<initialSize;i++){
            pool.addLast(ConnectionDriver.createConnection());
        }
    }

    public void releaseConnection(Connection connection){
        if(connection != null){
            synchronized (pool) {
                pool.addLast(connection);
                pool.notifyAll();
            }
        }
    }

    //若拿不到连接,则进入超时等待
    public Connection fetchConnection(long mills) throws InterruptedException {
        synchronized (pool) {
            if(mills < 0) {
                while(pool.isEmpty()){
                    pool.wait();
                }
                return pool.removeFirst();
            }
            long future = System.currentTimeMillis() + mills;
            long remaining = mills;
            while(pool.isEmpty() && remaining > 0){
                pool.wait(remaining);
                remaining = future - System.currentTimeMillis();
            }
            Connection result = null;
            if(!pool.isEmpty()){
                result = pool.removeFirst();
            }
            return result;
        }
    }
}

下面创建一个代理类来模拟数据库连接:

public class ConnectionDriver {

    static class ConnectionHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(method.getName().equals("commit")){
                TimeUnit.MILLISECONDS.sleep(100);
            }
            return null;
        }
    }

    public static final Connection createConnection(){
        return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader()
                ,new Class<?>[]{Connection.class}, new ConnectionHandler());
    }
}

最后,实际测试一下:

public class ConnectionPoolTest {
    static ConnectionPool pool = new ConnectionPool(10);
    static CountDownLatch start = new CountDownLatch(1);
    static CountDownLatch end;


    static class ConnectionRunner implements  Runnable {

        int count;
        AtomicInteger got;
        AtomicInteger notGot;

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

        public static void main(String[] args) throws InterruptedException {
            int threadCount = 10;
            end = new CountDownLatch(5);
            int count = 20;
            AtomicInteger got = new AtomicInteger();
            AtomicInteger notGot = new AtomicInteger();
            for (int i=0; i< threadCount ; i++) {
                Thread thread = new Thread(new ConnectionRunner(count,got,notGot),"ConnecntionRunnerThread");
                thread.start();
            }
            start.countDown();
            end.await();
            System.out.println("total invoke:" + threadCount * count);
            System.out.println("got connection:" + got.get());
            System.out.println("not got connection:" + notGot.get());

        }

        @Override
        public void run() {
            try {
                start.await();
            } catch (InterruptedException e) {
            }
            while (count > 0){
                try {
                    Connection connection = pool.fetchConnection(1000);
                    if(connection != null){
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConnection(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}

以上内容整理自《Java 并发编程的艺术》一书,源码使用Java 8编写,与原文略有差异。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容

  • layout: posttitle: 《Java并发编程的艺术》笔记categories: Javaexcerpt...
    xiaogmail阅读 5,802评论 1 19
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,757评论 14 507
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 2,070评论 0 14
  • 在证券公司也已经呆了两周了,整体感觉还不错。这个行业还是很有前景的,作为一个资金融通的渠道。 证券公司属于一个中介...
    爱chocolateyou阅读 481评论 3 3
  • 宝宝快六个月了,越来越觉得当母亲对孩子性格以及人格影响的重要性。希望她今后能享受世间的美!审美要从娃娃抓起,更要从...
    seekingdory阅读 198评论 3 0