JAVA多线程详解

1.前言

在JAVA内存模型(JMM)中已经讲解了为了提高处理器的效率,实现"高并发"的效果,在处理器和存储设备之间加入高速缓存来作为缓冲.因此,使用多线程可以更好的利用cpu资源.

2.目录

目录

3.多线程

3.1.相关概念

  • 进程:是程序一次执行过程,是系统运行程序的基本单位.系统运行一个程序即是一个进程从创建,运行到消亡的过程.一个进程就是一个执行中的程序,每个进程占有系统资源(如cpu时间,内存空间,文件,输入输出设备的使用权等).程序执行时,将被载入操作系统载入内存中

  • 线程:线程是比进程更小的执行单位.一个进程在执行的过程中可以产生多个线程,多个线程共享同一块内存空间和一组系统资源.因此系统在创建线程,切换线程,负担比进程小得多,被称作轻量级进程

  • 多线程:在同一时间点,运行多个线程(一个处理器在某一个时间点上永远都只能是一个线程,即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行).这里的同一时间点实际上一个线程执行一会然后其他线程再执行

  • 并行:当有多个线程在操作时,如果只有一个cpu,cpu将运行时间划分成若干个时间段,再讲时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态

  • 并发:当一个cpu执行一个线程时,另一个cpu可以执行另一个线程,两个线程互不抢占cpu资源,可以同时进行

3.2.线程状态

线程状态
  • 调用join()和sleep()方法,结束时都会回到Runnable状态,等待JVM调度

  • 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool),释放同步锁使线程回到可运行状态(Runnable)

  • 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool),同步锁被释放进入可运行状态(Runnable)

  • yleld()方法可以让一个running转台的线程转入runnable

3.3.同步机制

  • synchronized:可用来给对象,方法或者代码块加锁,锁定的是一个方法或者代码块时,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代码块,一个时间内只有一个线程能得到执行
  • monitor:java中的每个对象都有一个监视器,来监测并发代码的重入,在synchronized范围内,监视器发挥作用
  • wait/notify:必须存在于synchronized块中,这三个关键字针对的是同一个监视器,wait后,其它线程可以进入同步块执行
  • 当代码并不持有监视器的使用权时,wait或notify会抛出java.lang.IIIegalMonitorStateException
  • volatile:多线程的内存模型中,处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile作用:每次针对该变量的操作都激发一次load and save).同一实例的同一属性本身只有一个副本,但是多线程会缓存值,volatile不取缓存,直接取值,在线程安全的情况下加volatile会牺牲性能

3.4.线程基本类

  • Thread类重写run方法
new Thread(){
    @Override
    public void run() {
        super.run();
        System.out.println("Thread重写run方法开启一个线程");
    }
}.start();
  • Runnable接口传入Thread的构造器中
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("构造器传入runnable开启一个线程");
    }
}).start();
  • Callable:future模式,get()返回具体值,会阻塞,isDone返回boolean值,是否执行完毕,不会阻塞
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        MyCallable myCallable = new MyCallable();
        Future<String> future = executorService.submit(myCallable);
        boolean doneBefore = future.isDone();
        System.out.println(doneBefore);
        String s = future.get();
        System.out.println(s);
        boolean doneAfter = future.isDone();
        System.out.println(doneAfter);

        //或者
        MyCallable myCallableThread = new MyCallable();
        FutureTask<String> stringFutureTask = new FutureTask<>(myCallableThread);
        new Thread(stringFutureTask).start();
    }
    static class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(1000L);
            System.out.println("---1s后----");
            return "测试";
        }
    }
}
false
---1s后----
测试
true
---1s后----

3.5.高级多线程控制类

java1.5提供了一个非常高效实用的多线程包java.util.concurrent,提供了大量高级工具,帮助开发者编写高效,易维护,结构清晰的多线程程序

3.5.1.ThreadLocal类
  • 作用:保存线程的独立变量,对于一个线程类,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的副本变量,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本.常用语用户的登录控制,如记录session信息.

  • 实现:每个Thread都持有一个ThreadLocalmap类型的变量,以本身为key,亩变为value.主要方法是get()和set(),set之后再map里维护一个threadLocal →a,get()时将a返回.ThradLocal是一个特殊的容器

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("main");
new Thread(){
    @Override
    public void run() {
        super.run();
        threadLocal.set("Thread");
        System.out.println(threadLocal.get() + "==" + threadLocal);
    }
}.start();
new Thread(new Runnable() {
    @Override
    public void run() {
        threadLocal.set("runnable");
        System.out.println(threadLocal.get() + "==" + threadLocal);
    }
}).start();
System.out.println("主:" + threadLocal.get() + "==" + threadLocal);
Thread==java.lang.ThreadLocal@1225de4d
主:main==java.lang.ThreadLocal@1225de4d
runnable==java.lang.ThreadLocal@1225de4d
3.5.2.原子类(AtomicInteger,AtomicBoolean...)

使用atomic wrapper class,如AtomicInteger,或者使用自己保证原子的操作,则等同于synchronized

public class AtomicTest {
    private static AtomicInteger mAtomicInteger = new AtomicInteger(0);
    public static void main(String[] args) {
        new Thread(AtomicTest::printAdd,"线程1").start();
        new Thread(AtomicTest::printAdd,"线程2").start();
    }
    private static void printAdd() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "→" + mAtomicInteger.addAndGet(1));
        }
    }
}
线程1→1
线程1→3
线程1→4
线程2→2
线程2→5
线程2→6
  • addAndGet(int):以原子的方式将输入的值ActomicInteger中的值进行相加,返回相加后的值

  • CAS(Compare And Swap):即比较和交换,执行函数:CAS(V,E,N),内存位置(V),预期原值(E)和新值(N).从V上取值与E比较,如果与预期结果相等,则更新

//boolean值表示是否与期待值一致
public final boolean compareAndSet(int expect, int update) {
      return U.compareAndSwapInt(this, VALUE, expect, update);
}
//此方法使用与乐观锁
if(b.value.compareAndSet(old, value)){
   return ;
}else{
   //try again
   // if that fails, rollback and log
}
  • CAS自旋:乐观锁模式下,没有了锁概念,每条线程都直接先执行操作,计算完成后检测是否与其它线程存在共享数据竞争,如果没有则让此操作成功,如果存在共享数据竞争则可能不断地重新执行操作和检测,直到成功为止

CAS隐患

  • ABA问题,A改为B,又改为A,检测符合预期,实际发生了变化,解决方案就是追加版本号,JDK的Atomic包提供了类AtomicStampedReference,使用compareAndSet方法先检测当前引用是否等于预期引用,再判断当前标志是否等于预期标志
  • 循环时间开销太大
    当某一线程每次获取的值都被其他线程修改了,那么它会一直进行自旋比较,直到成功为止,cpu开销十分大
3.5.3.Lock类

lock:在java.util.concurrent包内

ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一样,两者都是为了解决同步的问题,处理资源争端而产生的技术.功能类似但有一些区别

  • lock更灵活,可以自由定义多把锁的加锁解锁顺序(synchronized要按照先加后解顺序)提供多种加锁方案,lock阻塞式,tryLock无阻塞式,lockInterruptily可打断式,还有tryLock的带超时时间版本,本质上和监视器锁(即synchronize是一样的)
  • 和Condition类结合性能更高

ReentrantLock
可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁

//创建实例
ReentrantLock r=new ReentrantLock();
//加锁
r.lock()或r.lockInterruptibly();
//释放锁  可以放在final块中
r.unlock()

ReentrantReadWriteLock
可重入读写锁

ReentrantReadWriteLock lock = new ReentrantReadWriteLock()
ReadLock r = lock.readLock();
WriteLock w = lock.writeLock();

两者都有lock,unlock方法.写写,写读互斥;读读不互斥,可以实现并发读的高效线程安全代码

4.容器类

BlockingQueue
阻塞队列,该类java.util.concurrent包下的重要类,通过对queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或去除元素,类似于一个管道,特别适用于先进先出策略的一些应用场景.在队列的基础上添加了多线程协作的功能,put会在队列满的时候阻塞,直到有空间时被唤醒;take在队列空的时候阻塞,直到有东西拿的时候才被唤醒.用于生产者-消费者模型

常见的阻塞队列

ArrayListBlockingQueue
LinkedListBlockingQueue
DelayQueue
SynchronousQueue

ConcurrentHashMap
高效的线程安全哈希map,通过分段锁实现高效的存取

5.管理类

用于管理线程,本身不是多线程,常见的管理类有:ThreadPoolExecutor, ExecutorService

 // 创建数组型缓冲等待队列
BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
 // ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 6, 50, TimeUnit.MILLISECONDS, bq);
// 创建任务
Runnable t1 = new TempThread();
//然后运行
tpe.execute(t1)
// 关闭自定义线程池
tpe.shutdown();

ThreadPoolExecutor构造器参数含义:

  • corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程
  • maximumPoolSize:线程最大值,线程的增长始终不会超过该值。
  • keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收.回收前处于wait状态
  • unit:时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS
  • workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)
  • threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。

6.总结

这一章节主要讲解多线程的一些相关知识,结合JAVA内存模块(JMM)对其运行机制有了整体的了解,知道了JAVA中如何使用多线程编程,如何避免并发安全问题


参考:https://www.cnblogs.com/wxd0108/p/5479442.html

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