线程基础、线程之间的共享和协作

什么是线程?

在说线程之前,先说说进程。那么什么是进程?进程是程序运行的环境,是操作系统分配资源的最小单元,每个进程都是独立的,其中包含的资源有CPU、内存空间、磁盘IO。而线程就是在一个进程内部的,共享当前进程的所有资源,是CPU调度和分配的最小基本单元。

了解了线程和进程的含义之后,我们在电脑上运行程序,就是启动一个进程,那为什么我们可以同时启动很多程序而系统不卡呢?原因在于CPU时间片轮转机制,CPU为每个进程分配了时间片,如果某个进程在时间片结束时还在运行,那么CPU将被剥夺并分配给另一个进程;如果某个进程在时间片结束前阻塞或者结束,那么CPU就会立即切换。CPU的时间片轮转机制以及上下文切换,在用户没有感知的情况下完成了看上去是同时运行的程序。

什么是并行和并发?

并行:假如有两个饮水机,两个人同时接水,并行是同时做一件事
并发:假如有一个饮水机,两个人交替接水,并发是交替去做,并发不能脱离时间,不然没有意义,一般是在一定时间内研究并发

多线程程序的优缺点

优点

1.可以充分利用操作系统资源
以前是一个线程执行,使用多线程后,可以提高CPU利用率,减少CPU空闲时间,提高并发量

2.加快响应用户的时间
以web项目为例,多开一个子域名,浏览器就多开一个线程去执行

3.可以使代码模块化、异步化、简单化
可以将相同功能代码模块化,将不是实时功能代码异步化,代码结构层次清晰明了

缺点

1.线程之间的安全问题
线程之间是共享进程所有资源的,所以在共享变量的操作有线程安全问题

2.线程之间的死锁问题
A线程持有a的锁,等待b的锁;B线程持有b的锁,等待a的锁,造成循环等待,形成死锁。解决这一类问题,可以考虑在A线程持有a锁,在一定时间内如果一直获取不到b的
锁,就主动释放a的锁。死锁还有很多种,这只是其中一种,具体的可参考百度百科[https://zhidao.baidu.com/question/1448029152492656860.html]

3.线程过多导致的当机问题
线程创建过多,不及时回收,容易造成资源浪费以及可能引发当机问题,合理创建线程,如果可以,多使用线程池技术

下面来说说JAVA线程

JAVA是支持多线程的语言,创建线程的方式有两种,一种是继承Thread类 ,X extend Thread;一种是实现Runnable接口,X implement Runnable。Thread是JAVA针对线程的抽象,Runnable是JAVA针对任务的抽象,这两个类都有一个run()方法,重写这个方法来实现业务逻辑。

JAVA线程运行图 :

线程.png

依据上图,我们可以看到,线程新建的时候,只是新建,当调用start()方法的时候,线程进入就绪状态,不是运行状态。在就绪状态,调用join()方法,可以将别的线程加入,让线程按照顺序执行。在运行期,调用sleep()方法,线程进入阻塞状态;sleep()时间到期,线程重新进入就绪状态;调用wait()方法,线程进入阻塞状态,但是这时候要唤醒线程需要调用 notify()、notifyAll()方法才可以。调用yield()方法,线程会让出CPU控制器,重新进入就绪状态。在阻塞状态,如果调用interrupt()方法,线程也会进入就绪状态。运行期间,如果run()方法执行完成,或者调用stop()方法(不推荐使用),setDemon()方法,会让线程进入结束状态。

yield()方法
线程出让自己的CPU时间片给操作系统,和其他线程一起去争夺CPU的时间片,但是不释放自己持有的资源。

setDemon()方法
设置成守护线程,守护线程是一种支持性线程,用于程序中后台调度与支持性工作,例如gc;守护线程与用户线程不同,守护线程在用户线程结束之时,立即结束,具体要看CPU分配的时间片。一般业务代码使用不到守护线程

stop()、interrupt()方法区别:

stop()是强制关闭线程,不释放线程内部持有的资源,例如锁,有引发死锁风险
interrupt()是给某一线程添加一个中断标志位,该线程不一定理会,其可以通过isInterrupt()方法来响应。

线程校验中断调用方法有两个:
isInterrupt()
Thread.interrupted()
其中,第二个方法会在校验中断标志位之后,清除标志位,置为false

最好不要自己实现interrupt()方法,因为如果调用阻塞方法sleep(),wait()的时候,线程是不会检测状态的,但是阻塞方法内部都抛出InterruptedException(),其对中断标志位是有响应的;sleep()方法在抛出InterruptedException之后,会将中断标志位置为false;因为如果不改变中断标志位,那么在阻塞之后,线程的资源得不到释放就被中断了,容易导致线程安全问题。

线程启动的start()方法,和重写的run()方法有何区别?

调用start()方法,将当前线程与操作系统进程挂钩。只能调用一次,如果第二次还调用,则会抛出异常;run()方法只是一个普通方法,与线程无关,只是线程业务逻辑的实现而已,本身可以被任意调用;

synchronized锁

synchronized是java关键字,当它修饰方法时,锁定的是调用这个方法的对象,修饰静态方法或者类时,锁定的是Class对象,所以synchronized锁定的是对象,任何非对象的数据,都不能使用synchronized锁,否则没有锁的效果;

volatile关键字

volatile可以保证共享变量的可见性,一被修改,其他线程可以立即看到。虽然同样进程里面的线程是共享所有资源的,但是JAVA里面还是进行了处理,所以如果需要可见性,就要使用volatile关键字。

ThreadLocal

ThreadLocal.png

ThreadLocal包含一个ThreadLocalMap内部类,而每个线程里面都存储了一个ThreadLocal.ThreadLocalMap属性,ThreadLocalMap里面包含一个Entry内部类,Entry实现了WeakReference<ThreadLocal<?>>,而我们使用ThreadLocal时候,经常使用的是ThreadLocal.get(),set(T value),remove()方法;
下面看看ThreadLocal.get()方法源码

public T get() {
        Thread t = Thread.currentThread();
        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();
    }

1:拿到当前线程;
2:从线程中获取ThreadLocalMap;
3:在ThreadLocalMap的Entry对象里面获取数据;

再看看ThreadLocal.set()方法源码

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

前两步和上面相同;
3:如果当前ThreadLocalMap存在,则替换
4:不存在就创建

使用ThreadLocal的好处是ThreadLocal是线程本地变量,每个线程独一份,不会受到多线程的影响,实现物理隔离,但是不保证数据一致性(也就是说不能使用ThreadLocal来操作共享变量,否则线程不安全)

强引用:Object o = new Object(),我们new出来的对象o,就是强引用。
软引用:当java虚拟机内存不足引发gc时,软引用会被打上标记并被回收,如果内存充足,则不会;而强引用就算内存不足引发gc时,也不会被回收,除非将强引用置为null(栈里面没有指向堆的引用的时候),或者等程序自动结束。
弱引用:当发生gc时,弱引用一定会被回收。
虚引用:最弱的引用,可以用来做为对象是否存活的监控。

坏处是 可能引发内存泄露,ThreadLocalMap存的是Entry,Entry实现了一个弱引用ThreadLocal类。如果Entry实现强引用的ThreadLocal类,则ThreadLocal百分之百引发内存泄露,因为Entry持有的不仅仅是ThreadLocal对象,还持有Object 对象,所以内存泄露是肯定的。但是为什么说可能呢?因为ThreadLocal.get(),set()方法里面都有内存回收机制,但不是每次都执行,所以可能导致内存泄露,所以我们在使用完ThreadLocal之后,一定要用ThreadLocal.remove()方法清除value值,防止可能引起的内存泄露。

线程之间的协作

等待和通知
wait() notify() notifyAll()

这里有个编程范式:

synchronized(obj){
    while(条件不满足){
        obj.wait();
    }
//TODO
}
synchronized(obj){
    obj.notify();
    //obj.notifyAll();
}

这里有个要点:wait()方法执行的时候,会自动释放锁资源;
notify(),notifyAll()是当synchronized方法执行完成,才会释放锁资源

等待超时模式实现一个线程池

//线程池
LinkedList<Object> pool = new LinkedList<Object>();
//获取资源
public Object getObj(long mills) throws InterruptedException{
    synchronized(pool){
     //永不超时
      if(mills <=0){
         while(pool.isEmpty()){
                pool.wait();
        }
      return pool.removeFirst();
      }else{
      long future = System.currentTimeMillis() + mills;
      long remain = mills;
      while(pool.isEmpty() && remain > 0){
           pool.wait(remain);
           //这里需要计算时间,防止超时(因为线程可能还是没有抢到资源)
           remain = future - System.currentTimeMillis();
      }
      Object obj = null;
      if(!pool.isEmpty()){
          obj = pool.removeFirst();
      }
      //可能返回的是null
      return obj;
    }
 }
}

//释放资源
public void releaseObj(Object obj) throws InterruptedException{
    if(obj != null){
      synchronized(pool){
        pool.addLast(obj);
        pool.notifyAll();
    }
  }
}

上面的代码中使用了 wait()范式,notifyAll()方法;
下面来介绍一下wait(),notifyAll(),notify(),sleep(),yield()方法对锁的影响
wait:线程在执行到wait方法的时候,主动释放锁资源,直到被notify,notifyAll唤醒之后,先去抢锁资源,然后执行wait方法后面的内容;
notify、notifyAll:线程执行到这两个方法时,不会释放锁资源,直到synchronized包含的内容走完,才会释放锁资源,所以建议将这两个方法写在synchronized内容的最后面;
sleep:线程进入阻塞状态,不会释放锁资源
yield:线程主动出让CPU控制权,然后和其他线程一起去争抢CPU的时间片,不会释放其持有的资源,包括锁。

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