JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中全部变量都是存在主存中的,对于全部线程进行共享,而每一个线程又存在本身的工做内存(Working Memory),工做内存中保存的是主存中某些变量的拷贝,线程对全部变量的操做并不是发生在主存区,而是发生在工做内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。而在多核处理器下,大部分数据存储在高速缓存中,若是高速缓存不通过内存的时候,也是不可见的一种表现。在Java程序中,内存自己是比较昂贵的资源,其实不只仅针对Java应用程序,对操做系统自己而言内存也属于昂贵资源,Java程序在性能开销过程当中有几个比较典型的可控制的来源。synchronized和volatile关键字提供的内存中模型的可见性保证程序使用一个特殊的、存储关卡(memory barrier)的指令,来刷新缓存,使缓存无效,刷新硬件的写缓存而且延迟执行的传递过程,无疑该机制会对Java程序的性能产生必定的影响。java
在java虚拟机进程中,执行程序代码的任务是由线程看来完成的。每一个线程都有一个独立的程序计数器和方法调用栈。程序计数器:pc寄存器,当线程执行一个方法时,程序计数器指向方法区中下一条要执行的字节码指令。方法调用栈:用来跟踪线程运行中一系列方法的调用过程,栈中的元素称为栈帧。每当线程调用一个方法,就会压栈一个新帧,帧用来保存方法的参数,局部变量,运算过程当中产生的临时数据。java虚拟机的主线程是它从启动类的main()方法开始运行。此外,用户也能够建立本身的线程,两种方式:继承 Thread 类,实现 Runnable 接口。
可是运行一个线程必须使用Thread.strat(),切记:1.不可直接运行run(),直接运行run()只是单纯的方法调用,并不会产出新的线程。2.不要随意覆盖start(),若是必须覆盖记得首先调用super.start()。线程是不会顺序执行的,一切都由操做系统调度决定,而且一个线程只能启动一次,第二次启动会抛出:IllegalThreadStateException,可是并不会影响以前启动的线程工做。编程
public class MyRunnable implements Runnable{ @Override public void run() { System.out.println("runnable running"); } } public class MyThread extends Thread{ @Override public void run(){ System.out.println("thread running"); } }
新建状态:new 语句建立的状态,此时它和其余java对象同样,仅仅在堆中被分配了内存。缓存
就绪状态:当一个线程被其余线程调用了start(),此时jvm会为它建立程序计数器和方法调用栈。处于改状态的线程位于可运行池,等待获取CPU的执行权。数据结构
运行状态:处于改状态的线程占用CPU,正在执行程序代码。若是计算机只有一个单核CPU那么永远hi只有一个线程处于改状态。只有处于就绪状态的线程才可能成为运行状态。多线程
阻塞状态:线程由于某些缘由放弃了CPU暂停执行。此时线程放弃CPU的执行权,直到进入就绪状态才可能再次变为运行状态。阻塞状态3中状况:并发
死亡状态:线程退出run(),有多是正常执行完成,也有可能碰见异常退出。可是都不会对其余线程形成影响。Thread类有isAlive()(新建与死亡状态返回false,其他状态返回true)判断线程是否存活。jvm
一个单核CPU在一个时刻只能执行一个机器指令。线程只有经过得到CPU才能执行本身的程序代码。所谓多线程的并发执行,其实从宏观上来看:各个线程轮流得到CPU的使用权,分别执行各自的任务。jvm采用抢占式调度模型,是指先让高优先级线程得到CPU。若是优先级相同,随机选择一个执行。处于运行状态的线程或一直执行,直到不得不放弃CPU,通常有以下缘由:ide
1. jvm让其放弃CPU转入就绪状态。 2. 线程因某些缘由进入阻塞状态。 3. 运行结束退出run()。
值得注意一点:java的线程优先级使用Thread.setPriority(int)设置,一般三个静态常量选择:Thread.MAX_PRIORITY(默认:10),Thread.MIN_PRIORITY(默认:1),Thread.NORM_PRIORITY(默认:5)。可是各个操做系统的线程优先级并不相同,因此为了确保程序可以在不一样平台正常执行,咱们只是用这三个值,不会使用1-10中的其余数字。经常使用方法:函数
Thread.sleep(long millis): 当前线程放弃CPU进入阻塞状态,通过milli毫秒后恢复就绪状态,不放弃对象锁的持有。性能
Thread.yield(): 让出CPU执行权进入就绪状态,给另外一个拥有相同或者大于优先级的线程,若是没知足条件的线程,则什么都不作。
Thread.join(): 当前线程调用另外一个线程的join(),而且等待被调用线程执行完后再继续执行。
Object.wait(): 当前线程必须拥有当前对象锁。若是当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,不然也会抛出IllegalMonitorStateException异常。waite()和notify()必须synchronized函数或synchronized block中进行调用。若是在non-synchronized函数或non-synchronized block中进行调用,虽然能编译经过,但在运行时会发生IllegalMonitorStateException的异常。
Object.notify(): 执行该方法的线程随机唤醒对象等待池中的一个线程,并将其装入对象锁池之中。
并发编程三个概念:
原子性:一个操做或者多个操做,要么所有成功,要么所有失败。
可见性:当多个线程访问同一变量时,一个线程修改了该变量的值,其余线程能当即看到修改后的值。
有序性:程序执行的顺序按照代码的前后顺序执行。(你觉得这是废话?请了解指令重排序)。这三个特性中2,3能够由volatile关键字保证(2.缓存一致性协议,3.禁止指令重排序),1只能由同步方式保证。
同步是解决资源共享的有效手段。当一个线程在操做共享变量的时候,其余线程只能等待。只有当该线程执行完同步代码块后,其余线程才能有机会操做共享资源。一般有以下几种同步方式:
synchorized关键字: 修饰方法或者使用同步代码块。
ReentrantLock重入锁对象: 锁住共享变量的操做。
使用并发数据结构对象:Atomic系列,Concurrent系列等。
可是同步的操做,代价较大,咱们应该尽量减小同步操做,是的一个线程能尽快的释放锁,减小其余线程执行的时间。因为等待一个锁的线程只有在得到了这把锁以后,
才能继续执行因此让持有锁的线程及时释放锁的至关重要的。
如下状况线程释放锁:
执行完同步代码块。
执行同步代码块的过程当中,碰见异常,线程死亡,锁被释放。
如下状况线程不会释放锁:
执行同步代码块的过程当中,执行了Thread.sleep(),当前线程放弃CPU开始睡眠进入阻塞状态,可是不会释放锁。
执行同步代码块的过程当中,执行了Thread.yield(),当前线程放弃CPU开始睡眠进入就绪状态,可是不会释放锁。
不一样的线程须要协做完成工做(一种状况是:线程2须要线程1的执行结果)。
- Object.wait(): 执行该放大的线程释放它持有的该对象的共享锁(前提时必须持有该共享锁),该线程进入对象等待池,等待其余线程将其唤醒。 - Object.notify(): 执行该方法的线程随机唤醒对象等待池中的一个线程,并将其装入对象锁池之中。
补充:
1.锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),因为这些线程在进入对象的synchronized方法以前必须先得到该对象的锁的拥有权。可是该对象的锁目前正被线程A拥有,因此这些线程就进入了该对象的锁池中。
2.等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(由于wait()方法必须出如今synchronized中,这样天然在执行wait()方法以前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。若是另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会所有进入该对象的锁池中,准备争夺锁的拥有权。若是另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池。步骤以下(后面会有代码实例):
1. t1执行s的一个同步代码块,t1持有s的共享锁,t2在s的锁池中等待。 2. t1在同步代码中执行s.wait(0,t1释放s的共享锁,进入s的等待池。 3. s的锁池中t2得到共享锁执行s的另外一同步代码块。 4. t2在同步代码块中执行s.notify(),JVM将t1从s的等待池转入s的锁池。 5. t2完成同步代码,释放锁,t1得到锁继续执行同步代码。
eg:两个线程,一个线程将某个对象的某个成员变量的值加1,而另一个线程将这个成员变量的值减1.使得该变量的值始终处于[0,2].初始值为0:
当一个线程处于阻塞状态时,另外一个线程调用阻塞线程的interrupt(),阻塞线程收到InterruptException,并退出阻塞状态,开始进行异常处理。代码:
@Override public void run() { System.out.println("runnable running"); try { Thread.sleep(1l); } catch (InterruptedException e) { //-----start异常处理---- e.printStackTrace(); //-----end异常处理----- } }
并发编程的知识很是复杂,以上只是一些皮毛,后续还将学习Synchronized,ReentrantLock,Future,FutureTask,Executor,Fork/Join,CompletableFuture,Map-Reduce等相关知识,最后用一个实际项目来完成这部分知识的学习。