Java 多线程

1 简述

1.1 线程机制

Java使用的是抢占式的线程机制,调度机制周期性地切换上下文,切换线程,从而为每个线程提供时间片。看似同时执行,其实是不停地切换。在这种机制下,一个线程的阻塞不会导致整个进程阻塞。

优先级较低的线程仅仅是执行的频率较低,不会得不到执行。

要实现线程行为,你必须显示地将一个任务(Runnable)附着到线程(Thread)上。Thread类只是驱动赋予它的任务Runnable。

1.2 阻塞(se第四声)

程序中一个任务因为该程序控制范围外的因素(条件通常是IO),而不能继续执行,这个任务线程被阻塞了。

2 Executor 线程池

用new Thread()的方式不利于性能优化,所以采用线程池替代,达到线程的复用。首选CachedThreadPool回收旧线程停止创建新线程。

ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Runnable() {
        @Override
        public void run() {

        }
});

Runnable设返回值,需要实现Callable接口call()方法,并且必须使用ExecutorService.submit()方法调用。
使用 Future.get() 获取返回值。
例如:

public class CallableAndFuture {
    static class MyThread implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello world";
        }
    }

    static class MyThread2 implements Runnable {
        @Override
        public void run() {

        }
    }

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future<String> future = threadPool.submit(new MyThread());

        try {
            System.out.println(future.get());
        } catch (Exception e) {

        } finally {
            threadPool.shutdown();
        }
    }
}

3 线程安全

3.1 实质

线程安全其实就是解决共享资源竞争的问题。
解决方法是:当一个资源被一个任务使用时,加锁。

解锁的时候,下一个要使用资源的任务并没有按照排队的方式按次序来获取,而是通过竞争获取资源。可以通过yield()和setPriority()来给线程调度器提供建议,这效果取决于具体平台和JVM实现。

3.2 synchronized加锁

对于某个特定对象,其所有的synchronized方法共享同一个锁。
一个任务可以多次获得对象的锁。

判断是否应该加锁:如果你正在写一个变量,这个变量接下来将被另一个线程读取;你正在读一个上一次已经被另一个线程写过的变量。以上两种情况都必须使用同步。并且,读写线程都必须用相同的监视器锁同步。

3.3 使用显式的Lock对象加锁

除了synchronized方式加锁之外,还可以用Lock对象:

private Lock lock = new ReentrantLock();//重入锁
......
lock.lock();
try{
......
return ...;//在try中return确保unlock()不会过早发生
}finally{
lock.unlock();
}

和synchronized的区别:tryLock()

  1. 可以尝试着获取锁,最终获取失败。
  2. 可以尝试着获取锁一段时间,然后放弃获取。

如果其他的线程已经获取了这个锁,你可以决定离开,去执行其他一些事,而不是等待直至这个锁释放。提供了比Synchronized更细粒度的控制力。(例如可以释放当前锁之前,捕获下一节点的锁)

4 volatile

volatile关键字主要提供2种作用:

  1. 可见性:当使用volatile关键字去修饰变量的时候,所有线程都会直接读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的
  2. 禁止指令重排序:应用在标志位

5线程本地存储

防止共享资源产生冲突的第二种方式是根除变量的共享,使用ThreadLocal。

6 终结任务

线程的各个状态


线程的各个状态

停止线程主要使用标志位,在run()方法中加以判断,是否要执行。

  1. 自己定义一个volatile boolean的标志位
  2. 使用interrupt()方法,但是这个方法也是打个停止的标志,并不是真正的停止线程,还是要在run()方法中调用Thread.interrupted()方法判断。

7 线程之间的协作

yield()方法:调用的线程放弃cpu时间让给别的线程。
join()方法:调用的线程先执行。

7.1 wait()

wait()是object的方法,它会释放锁,而sleep()和yield()不会释放。
因此在该对象(未锁定的)中的其他synchronized方法可以在wait()期间被调用。

只能在同步控制方法或者同步控制块里调用wait()、notify()、notifyAll(),因为要操作对象的锁。

wait()必须用一个检查感兴趣的条件的while循环包围,本质是检查感兴趣的条件,并在条件不满足的情况下返回到wait()方法中。

Thread1:
synchronized(sharedMonitor){
  <setup condition for Thread2>
  sharedMonitor.notify();
}
//错误的使用:时机太晚,错失信号,盲目进入wait(),产生死锁。
Thread2:
while(someConditon){
  synchronized(sharedMonitor){
    sharedMonitor.wait();
  }
}
//正确的使用:防止在someCondition变量上产生竞争条件
Thread2:
 synchronized(sharedMonitor){
    while(someConditon){
         sharedMonitor.wait();
    }
 }

7.2 notify()和notifyAll()

notify():在众多等待同一个锁的线程中,只有一个会被唤醒去获取锁。
notifyAll():在众多等待同一个锁的线程中,全部会被唤醒去共同竞争锁。
两个同样最终只有一个线程能获取到锁。

可能有多个线程某单个对象上处于wait状态,因此调用notifyAll()比notify()更安全。
只有一个线程实际处于wait()状态,这时可以用notify()代替notifyAll()优化;

如果有多个线程在等待不同条件,使用notify()就不能知道是否唤醒了恰当的任务。

8 死锁

某个线程在等待另一个线程,而后者又在等待别的线程,这样一直下去,直到这个链上的线程又在等待第一个线程释放锁。这得到了一个状态:线程之间相互等待的连续循环,没有哪个线程能继续。这被称为死锁

死锁的4个条件:

  1. 互斥条件(资源的特点):线程使用的资源中至少有一个是不能共享的。
  2. 占用请求条件(单个线程的特点):至少有一个线程,它持有一个资源,并且等待获取另一个当前被别的线程持有的资源。
  3. 不可剥夺条件(单个线程的特点):已持有的资源不能被别的线程抢占,而它(持有资源的线程)自己也不会主动去抢别的线程的资源。
  4. 循环等待条件(多个线程形成的特点):必须有循环等待。

解决:让一方先放弃资源作出让步。

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