一个程序运行后被抽象为一个进程;
一个程序至少有一个进程,一个进程至少有一个线程.
线程是程序执行时的最小单位,是CPU调度和分派的基本单位;
它是进程的一个执行流,一个进程可以由很多个线程组成; 线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量;
线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行
线程由阻塞状态,只能进入到就绪状态,而不能直接变为运行状态
单核CPU,同一时间只能有一个线程运行,不同系统由不同的策略分配给各个线程执行时间;
多核CPU,同时可有多个线程并行(不是并发)
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪,notify通知);
以下情况会发生阻塞:
sleep
调用IO方法等待返回
等待获取资源监视器monitor(synchronize)
wait等待获取notify通知
调用suspend()将线程挂起(容易死锁,避免使用)
yield(不会阻塞,直接由运行状态变为就绪状态)
new MyThread().start(); 复制代码
new Thread(new MyRunnable()).start(); 复制代码
暂停执行一段时间,进入阻塞状态;
时间过去后,进入就绪状态
把指定的线程加入到当前线程,让一个线程等待另一个线程执行完以后再执行,可以将两个交替执行的线程合并为顺序执行的线程
... try { threadA.start(); threadA.join(); } catch (InterruptedException e) { e.printStackTrace(); } //当前线程等待threadA线程执行完以后,再往下执行 ... 复制代码
线程让步:暂时让出自己的时间片给通优先级线程
当前线程由执行状态,转入就绪状态,不会阻塞线程
只是转入就绪状态,让线程调度器重新调度一次:
与当前线程优先级相同或更高,且处于就绪状态的线程获取执行,进入运行状态。 也有可能yield以后调度器重新立刻执行该线程。
Daemon Thread,又称“守护线程”,在后台运行,为其它线程提供服务;
所有前台线程死亡,后台线程自动死亡;
setDaemon(true),在start()之前设置 isDaemon()判断是否为后台线程 复制代码
mThread.setPriority(Thread.MAX_PRIORITY);//10 mThread.setPriority(Thread.NORM_PRIORITY);//5 mThread.setPriority(Thread.MIN_PRIORITY);//1 可以直接写数字,但是不同操作系统优先级不同,也不能很好的对应Java的10个优先级 所以,尽量使用Thread提供的优先级静态字段。 复制代码
Thread mThread = new Thread() { public void run() { if (mThtread.isInterrupted()) { //收尾 return; } } }; mThread.start(); mThread.stop();//强行终止 mThread.interrupt();//标记,配合终止 复制代码
仅仅返回当前状态
返回当前状态,并重置该状态
The interrupted status of the thread is cleared by this method
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中做收尾工作 复制代码
方法、代码块
保证方法或代码块内部对资源的互斥访问;
即同一时间,同一个monitor监视的代码,最多只能有一个线程访问
保证线程之间,对监视资源的数据同步;
即任何线程在获得monitor的第一时间,现将共享内存中的数据复制到自己的缓存中;
任何线程在释放monitor的第一时间,将自己缓存中的数据复制到共享内存中
同样是“加锁”机制,使用方式更灵活,但也更麻烦
Java 并发:Lock 框架详解
读写锁...
程序的最小执行单位,不能被分割执行,如 a = 1+2;
注意:i++不是原子操作 --> tmp = i+1; i = tmp
轻量级的synchronize
作用和volatile基本一致,对于操作基本类型做了封装,更方便;
AtomicInteger、AtomicBoolean...
AtomicInteger ato = new AtomicInteger(); ato.addAndGet(1); ato.getAndAdd(2); 复制代码
无论是线程安全问题,还是衍生出的锁机制问题,它们的核心都在于“保证资源的安全性”,而不是某个方法或某几行代码
数据的安全性
多个线程访问共同的资源时,在某一个线程对资源进行写操作的中途,其它线程对该资源进行读、写,导致出现了数据的错误。
对资源进行访问限制
使同一时间只有一个线程可以访问资源,保证数据的准确性。
线程释放共享资源锁,进入等待队列,直到被再次唤醒
唤醒该共享资源锁上等待的单个/全部线程,唤醒哪个和顺序:取决于优先级和JVM
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(); } 复制代码