转载

Java线程

一个程序运行后被抽象为一个进程;

一个程序至少有一个进程,一个进程至少有一个线程.

线程

线程是程序执行时的最小单位,是CPU调度和分派的基本单位;

它是进程的一个执行流,一个进程可以由很多个线程组成; 线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量;

线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行

并行、并发

  • 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行;
  • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

二、线程生命周期

Java线程
  • 新建(New):创建Thread对象
  • 就绪(Runnable):start,但未运行,虚拟机为其创建方法调用栈和程序计数器,但运行时间取决于JVM调度
  • 运行(Running):
  • 阻塞(Blocked):线程暂停等待某个条件发生
  • 终止(Dead):正常执行结束、Exception或Error、stop();(isAlive()判断是否终止)

线程由阻塞状态,只能进入到就绪状态,而不能直接变为运行状态

运行和阻塞

运行

单核CPU,同一时间只能有一个线程运行,不同系统由不同的策略分配给各个线程执行时间;

多核CPU,同时可有多个线程并行(不是并发)

阻塞

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪,notify通知);

以下情况会发生阻塞:

  • sleep

  • 调用IO方法等待返回

  • 等待获取资源监视器monitor(synchronize)

  • wait等待获取notify通知

  • 调用suspend()将线程挂起(容易死锁,避免使用)

  • yield(不会阻塞,直接由运行状态变为就绪状态)

三、创建线程

Thread

new MyThread().start();
复制代码

Runnable

new Thread(new MyRunnable()).start();
复制代码

Future、Callable【暂未整理】

线程池【暂未整理】

四、线程控制

4.1 sleep()

暂停执行一段时间,进入阻塞状态;

时间过去后,进入就绪状态

4.2 join()

把指定的线程加入到当前线程,让一个线程等待另一个线程执行完以后再执行,可以将两个交替执行的线程合并为顺序执行的线程

...
        try {
            threadA.start();
            threadA.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //当前线程等待threadA线程执行完以后,再往下执行
        ...
复制代码

4.3 yield()

线程让步:暂时让出自己的时间片给通优先级线程

当前线程由执行状态,转入就绪状态,不会阻塞线程

只是转入就绪状态,让线程调度器重新调度一次:

与当前线程优先级相同或更高,且处于就绪状态的线程获取执行,进入运行状态。 也有可能yield以后调度器重新立刻执行该线程。

4.4 后台线程

Daemon Thread,又称“守护线程”,在后台运行,为其它线程提供服务;

所有前台线程死亡,后台线程自动死亡;

setDaemon(true),在start()之前设置
isDaemon()判断是否为后台线程
复制代码

前台/后台线程

  • 创建的线程默认是前台线程
  • setDaemon(true)设置为后台线程
  • 在前台/后台 线程中创建的线程 默认为 前台/后台

线程优先级

mThread.setPriority(Thread.MAX_PRIORITY);//10
    mThread.setPriority(Thread.NORM_PRIORITY);//5
    mThread.setPriority(Thread.MIN_PRIORITY);//1
    可以直接写数字,但是不同操作系统优先级不同,也不能很好的对应Java的10个优先级
    所以,尽量使用Thread提供的优先级静态字段。
复制代码

4.5 停止线程

stop()

  • 立即停止,强行中断
  • 对于代码逻辑不可控,粗暴式中断

interrupt()

  • 只是一个标记
  • 需要当前线程的逻辑支持,自行中断
Thread mThread = new Thread() {
    public void run() {
        if (mThtread.isInterrupted()) {
            //收尾
            return;
        }
    }
};

mThread.start();
mThread.stop();//强行终止
mThread.interrupt();//标记,配合终止
复制代码

判断interrupted状态:

thead.isInterrupted()

仅仅返回当前状态

Thread.interrupted()

返回当前状态,并重置该状态

The interrupted status of the thread is cleared by this method

interrupt()引起的异常

  • 当mThread.interrupt()时,sleep、join、wait会抛异常 ,并重置了该状态
    true --> false
  • SystemClock.sleep(2000),内部处理了异常,并从新设置为中断状态
    true --> false --> true
mThread.interrupt();//标记,配合终止

Thread mThread = new Thread() {
    public void run() {
    
        //只返回状态
        if (mThread.isInterrupted()) {
            //收尾
            return;
        }
        
        //返回状态,并重置状态
        if (Thread.interrupted()) {
            //收尾
            return;
        }

        try {
            Thread.sleep(2000);
            //interrupt()时,能唤醒sleep、join、wait
            //内部使用Thread.interrupted()检查、重置、抛异常
        } catch (InterruptedException e) {
            e.printStackTrace();
            //收尾
            return;
        }

        //被打断,不会重置状态,内部实现里又重新设置回来了
        SystemClock.sleep(2000);
    }
};

总结:
在mThread.isInterrupted()、Thread.interrupted()、InterruptedException中做收尾工作
复制代码

五、线程同步:

5.1 synchronize

使用位置:

方法、代码块

锁类型:

  • 对象锁:
    每个对象都会有一个 monitor 对象,通常会被称为“内置锁”或“对象锁”;
    类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
  • 类锁:
    针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁;
    每个类只有一个 Class 对象,所以每个类只有一个类锁。

synchronize本质

互斥性:

保证方法或代码块内部对资源的互斥访问;

即同一时间,同一个monitor监视的代码,最多只能有一个线程访问

Java线程

同步性:

保证线程之间,对监视资源的数据同步;

即任何线程在获得monitor的第一时间,现将共享内存中的数据复制到自己的缓存中;

任何线程在释放monitor的第一时间,将自己缓存中的数据复制到共享内存中

Java线程

5.2 Lock 【暂未整理】

同样是“加锁”机制,使用方式更灵活,但也更麻烦

Java 并发:Lock 框架详解

读写锁...

5.3 原子操作

程序的最小执行单位,不能被分割执行,如 a = 1+2;

注意:i++不是原子操作 --> tmp = i+1; i = tmp

volatile

轻量级的synchronize

  • volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存);
    这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性
  • 只对基本类型的赋值操作和对象的引用赋值操作有效;
    自增/减,不是原子操作,不保证

Atomic类

作用和volatile基本一致,对于操作基本类型做了封装,更方便;

AtomicInteger、AtomicBoolean...

AtomicInteger ato = new AtomicInteger();
ato.addAndGet(1);
ato.getAndAdd(2);
复制代码

5.4 总结

无论是线程安全问题,还是衍生出的锁机制问题,它们的核心都在于“保证资源的安全性”,而不是某个方法或某几行代码

线程安全问题的本质

数据的安全性

多个线程访问共同的资源时,在某一个线程对资源进行写操作的中途,其它线程对该资源进行读、写,导致出现了数据的错误。

锁机制的本质

对资源进行访问限制

使同一时间只有一个线程可以访问资源,保证数据的准确性。

六、线程通信

wait

线程释放共享资源锁,进入等待队列,直到被再次唤醒

notify、notifyAll

唤醒该共享资源锁上等待的单个/全部线程,唤醒哪个和顺序:取决于优先级和JVM

  • 在synchronized方法或代码块中使用,否则报错:IllegalMonitorStateException
  • 调用的是当前monitor的Object方法,而不是Thread方法
    虽然在Thread中使用,但阻塞是因为在等monitor释放并获取通知
private String testStr;

public void setStr(String str) {
    synchronized (obj){
        this.testStr = str;
        obj.notify();
    }
}

public String getStr() {
    /**
     为什么不用if而要用while?
     因为wait不确定会被谁唤醒(notify、interrt)
     1. wait()一般都是这样搭配的标准写法
     2. wait不只会被notify唤醒,还可能会被intrupt唤醒;
        此时如果是if的话,wait被唤醒,就不查是否为null直接return了
     */
    synchronized (obj){
        //if (testStr == null){
        while (testStr == null) {
            try {
                obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return testStr;
        }
    }
}
    
#范式
    等待方:
    synchronized( 对象 ) {
        //not if
        while(条件) {
            对象.wait();
        }
        对应的处理逻辑
    }
    
    通知方:
    synchronized( 对象 ) {
        改变条件;
        对象.notifyAll();
    }
复制代码
原文  https://juejin.im/post/5e83086de51d4547170a88ac
正文到此结束
Loading...