线程:线程是进程当中独立运行的子任务。java
javaw.exe主要用于启动基于GUI的应用程序。web
java.exe执行应用日志再在控制台显示输出与错误信息。编程
javaws.exe是用来启动经过web来描述的项目,咱们须要一个jnlp文件,来描述javaws.exe须要运行的程序安全
用start()方法启动线程之后,只是通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个线程让系统安排一个时间来调用Thread的run()方法,具备异步执行的效果。而run()不是异步的,而是同步执行的。Thread.sleep()会让当前线程阻塞,若是在主线程中经过start()调用时,调用的线程是main,若是直接用run()调用的话则直接是当前线程调用Thread-0多线程
Thread.curredntThread()返回代码段在被那个线程调用的信息。并发
Thread.currentThread().isAlive() 当前线程是否处于活跃状态。异步
在自定义线程类时,若是线程类是继承java.lang.Thread的话,那么线程类就可使用this关键字去调用继承自父类Thread的方法,this就是当前的对象。ide
Thread.currentThread()能够获取当前线程的引用,通常都是在没有线程对象又须要得到线程信息时经过Thread.currentThread()获取当前代码段所在线程的引用。性能
http://www.javashuo.com/article/p-dkyiqfem-nh.html学习
在指定的毫秒内让当前“正在执行的线程”休眠(暂停执行),这个“正在执行的线程”是指this.currentThread()返回的线程。
Thread.currentThread().getId();得到当前线程的id
(1)当run方法完成后线程终止
(2)使用stop方法强行终止线程,不推荐使用,和suspend及resume同样被废弃调了
(3)使用interrupt方法中断(线程不会真的中止)
interrupt()仅仅是在当前线程中打了一个中止标记,并非真的中止线程,判断线程是否中止的方法
1)this.interrupted():测试当前线程是否已是中断状态,并将标志状态清除为false。 是静态方法,能够经过Thread.interrupted()来进行判断
2)this.isInterrupted():测试线程Thread对象是否已是中断状态,但不清楚状态标志。是非静态方法,经过对象进行判断。
/**
* @author 赵洪坤
* @日期2018年6月6日
*/
public class ExtendThread extends Thread {
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 50000; i++) {
if (this.interrupted()) {
System.out.println("已是中止状态了!我要退出");
throw new InterruptedException();
}
System.out.println("i=" + (i + 1));
}
System.out.println("for循环又继续了");
} catch (InterruptedException e) {
System.out.println("进入interrupt异常");
e.printStackTrace();
}
}
}
/**
* @author 赵洪坤
* @日期2018年6月6日
*/
public class Main {
/**
* @author 赵洪坤
* @日期2018年6月6日
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) {
try {
ExtendThread extendThread = new ExtendThread();
extendThread.start();
Thread.sleep(200);
extendThread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(1)stop()中止线程至关于电脑拔掉电源,可能使一些清理邢的工做得不到完成。
(2)对锁的对象进行了“解锁”,致使数据得不到同步处理,出现数据不一致
(1)缺点-使用不当,容易形成公共的同步对象的独占
(2)缺点-不一样步
放弃当前cpu的资源,将它让给其它的任务去占用cpu的执行时间。但放弃的时间不肯定,有可能刚刚放弃,立刻又得到cpu时间片。
设置线程执行的级别,级别越大越有可能先执行完,1-10个级别,但不是绝对的
设置线程为守护线程thread。setDaemon(true); 守护线程是一种特殊的线程,它的特性有陪伴的含义,当进程中不存在非守护线程了,则守护线程自动销毁。典型的守护线程是垃圾回收线程。
关键字synchronized去的的锁都是对象锁,而不是把一段代码或者方法当作锁,因此那个线程先执行带synchronized关键字的方法,那个线程就持有该方法所属对象的锁Lock,那么其它线程只能呈等待状态,前提是多个线程访问的是同一个对象。若是多个线程访问多个对象,则JVM会建立多个锁。
调用关键字sychronized声明的方法必定是排队运行的。另外要紧紧记住“共享”这两个字,只有共享资源的读写访问才须要同步化,若是不是共享资源,那么根本就没有同步的必要。
(1)当A线程调用anyObeject对象加入synchronized关键字的X方法时,A线程就得到了X方法锁,更准确的讲,是得到了对象的锁,因此其余线程必须等A线程执行完毕才能够调用X方法,但B线程能够随意调用其余的非synchronized同步方法。
(2)当A线程调用anyObject对象加入sychronized关键字的X方法时,A线程就得到了X方法所在的对象的锁,因此其余线程必须等A线程执行完毕才能够调用X方法,而B线程若是嗲用声明了synchronized关键字的费X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才能够调用。这时A线程已经执行了一个完整的任务,也就是说username和password这两个示例变量已经同时被赋值,不存在脏读的基本环境。
当存在父子类继承关系时,子类是彻底能够经过“可重入锁”调用父类的同步方法的。
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
当父类的方法加上synchronized之后,子类继承父类的方法之后,子类也要加synchronized才能同步。
⑥、关于synchronized代码块同步
在使用同步synchronized(this)代码块时须要注意的时,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对同一个object中全部其余synchronized(this)同步diamante块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。
①synchronized(this)代码块锁定当前对象,监视器为当前对象
例:public void doLong(){
synchronized(this){
………………
}
}
②将任意对象做为对象监视器
例:public void doLong(){
synchronized(anything){
…………
}
}
总结: synchronized同步方法和synchronized(this)同步代码块都能达到阻塞的状态,锁非this对象具备必定的优势:若是一个类中有不少个synchronized方法,这是虽然能实现同步,可是会受到阻塞,因此影响运行效率;但若是使用同步代码块锁非this对象,则异步的不与其余锁this同步方法争抢this锁,则可大大提升运行效率。可见使用“synchronized(非this对象x)同步代码块”格式进行同步操做时,对象监视器必须是同一个对象。若是不是同一个对象监视器,运行的结果就是异步调用了,就会有交叉运行。
③synchronized public static void printA(){
………………
}
总结:关键字synchronized还能够应用在static静态方法上,若是这样写,那是对当前的*.java文件对应的Class类进行持锁。synchronized关键字加到static静态方法上是给Class类上锁,二synchronized关键字加到非static静态方法上是给对象上锁。
String常量池会形成不一样的变量,相同值时线程的锁会认为是同一把锁。使用对象锁能够改善这种情况。
解决办法:
关键字volatile的做用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
问题:这种结构会形成私有堆栈中的值和公有堆栈中的值不一样步。
经过使用volatile关键字,强制的从公共内存中读取变量的值。
1)关键字volatile是线程同步的轻量级实现,因此volatile性能确定比synchronized要好,而且volatile只能修饰于变量,而synchronized能够修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上获得很大提高,在开发中使用synchronized关键字的比率仍是比较大的
2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
3)volatile能保证数据的可见性,但不能保证原子性(由于它不具有同步性,也就不具有原子性);而synchronized能够保证原子性,也能够间接保证可见性,由于它会将私有内存和公共内存中的数据作同步。
4)再次重申一下,关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。线程安全包含原子性和可见性两方面,java的同步机制都是围绕这两方面来确保线程安全的。
volatile增长了实例变量在多个线程之间的可见性,可是它不具有同步性,因此也就不具有原子性。volatile主要使用的场合是在多个线程中能够感知实例变量被更改,而且能够得到最新的值使用,也就是用多线程读取共享变量时能够获取最新值使用。关键字volatile提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。若是修改实例变量中的数据,好比i++,也就是i=i+1,则这样的操做其实并非一个原子操做,也就是非线程安全的。操做过程:
1)从内存中取出i的值
2)计算i的值
3)将i的值写到内存中
在第2步计算值得时候,另一个线程也修改i的值,这个时候就会出现脏读数据。解决的办法就是使用synchronized关键字。
图示演示volatile出现非线程安全的缘由:
1)read和load阶段:从主存复制变量到当前线程工做内存
2)use和assign阶段:执行代码,改变共享变量值
3)store和write阶段:用工做内存数据刷新主存对应变量的值
在多线程环境中,use和assign是屡次出现的,但这一操做并非原子性,也就是在read和load以后,若是主内存count变量发生修改以后,线程工做内存中的值因为已经加载,不会产生对应的变化,也就是私有内存和公共内存中的变量不一样步,因此计算出来的结果会和预期不同,也就出现了非线程安全问题。对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工做内存的值时最新的,例如线程1和线程2在进行read和load的操做中,发现内存中count的值都是5,那么都会加载这个最新的值。也就是说,volatile关键字解决的事变量读时的可见性问题,但没法保证原子性,对于多个线程访问同一个实例变量须要加锁同步。
总结:关键字synchronized能够保证在同一时刻,只有一个线程能够执行某一方法或某一代码块。它包含两个特征:互斥性和可见性。同步synchronized不只能够解决一个线程处于不一致的状态,还能够保证进入同步方法或同步代码块的每一个线程,都能看到由同一个锁保护以前的修改效果。学习多线程并发,要着重“外练互斥,内修可见”,这是掌握多线程,学习多线程并发的重要技术点。
④经过管道进行线程间通讯:字节流、字节流
⑥方法join与异常
父线程异常,子线程继续运行
⑨生产者/消费者模式
1. 传统的单例模式
你们都知道,单例模式主要分为:懒汉模式和饿汉模式。当咱们在使用单例模式时,考虑到延迟加载,懒汉模式确定是必须的。可是懒汉模式有一个很大的缺点,那就是线程不安全。咱们为了解决这个问题,发明了双重检查锁定的写法,以下:
public class SingleTon {
private static SingleTon instance = null;
private SingleTon(){
}
public static SingleTon getInstance(){
if(instance == null){ // 1
synchronized (SingleTon.class){
if(instance == null){
instance = new SingleTon(); // 2
}
}
}
return instance;
}
}
如上代码所示,若是第一次检查instnace不为null的话,那么就不须要去获取锁来进行instance的初始化。所以,看上去能够大大的下降synchronized带来的性能开销。
双重检查锁定看上去很是的完美,可是倒是一个错误的优化。当一个线程在执行到1时,读取到instance不为null时,instance引用的对象可能尚未初始化完毕。
2.问题的根源
在前面的代码中,instance = new SingleTon();是用来建立对象。这一行代码能够分解为以下三行伪代码:
memory = allocate(); //1.分配对象内存空间
ctorInstance(memory); //2.初始化对象
instance = memory; //3.设置instance指向刚分配的内存地址
由于2和3不存在数据依赖性,因此可能会被重排序。2和3重排序以后的执行顺讯可能以下:
memory = allocate(); //1.分配对象内存空间
instance = memory; //3.设置instance指向刚分配的内存地址,注意,此时对象尚未被初始化
ctorInstance(memory); //2.初始化对象
这里可能有人对重排序存在疑惑,若是想要理解什么是重排序,为何要重排序等等缘由,强烈推荐:方腾飞、魏鹏、程晓明三位老师的《Java 并发编程的艺术》。这里我就不对这部分进行展开,主要是本身太菜了,惧怕对这部分的解释很差。
咱们知道instance = new SingleTon()这一步可能会被重排序以后,如今咱们来看看什么状况下可以致使问题。假设有两个线程,ThreadA和ThreadB,这两个线程都在调用SingleTone的getInstance方法来获取一个SingleTon的对象。执行顺序可能出现以下状况:
因为单线程内须要遵照intra-thread semantics,从而能保证ThreadA的执行结果不会被改变(全部线程在执行Java程序必须遵照intra-thread semantics,而intra-thread semantics保证全部的重排序在单线程里内,程序的执行结果不会被改变)。可是当ThreadB在按照上图的顺序在执行时,ThreadB将看到一个尚未被初始化的SingleTon对象。
从咱们的程序代码中能够看出来,当instance = new SingleTon()发生了重排序,ThreadB在if(instance == null) 判断出false,接下来将访问instace所引用的对象,可是此时这个对象可能尚未被ThreadA初始化完毕。
上表就是对上面的流程图的一个总结。咱们知道,最后ThreadB可能会返回一个为未初始化的对象。
在知道了问题的根源以后,咱们能够想出两个办法来实现线程安全的延迟初始化。
1.不容许2和3重排序。
2.容许2和3重排序,可是不容许其余线程“看到”这个重排序。
3.解决方案
前面解释了双重检查锁定问题的根源,而且列出了两种解决思路。这里,咱们将对这两种思路进行展开。
(1).基于volatile的解决方案
这个解决方案是很是的简单,只须要将咱们以前的那个instance变量使用volatile关键字来修饰就好了。以下:
public class SingleTon {
private volatile static SingleTon instance = null;
private SingleTon(){
}
public static SingleTon getInstance(){
if(instance == null){ //1
synchronized (SingleTon.class){
if(instance == null){
instance = new SingleTon(); //2
}
}
}
return instance;
}
}
是否是很是的简单?固然咱们这里的目的固然不是简单的实现解决方案,而是详细的解释为何须要这样作。
memory = allocate(); //1.分配对象内存空间
ctorInstance(memory); //2.初始化对象
instance = memory; //3.设置instance指向刚分配的内存地址
因为instance是volatile变量,因此上面的代码中3至关因而对volatile变量进行写的操做,也就是所谓的volatile写。根据《Java 并发编程的艺术》的P43,咱们知道对于一个volatile写,编译器会在volatile写的前面加入一个StoreStore内存屏障,用来防止前面的普通写与下面的volatile写进行重排序;在volatile写的后面加入一个StoreLoad屏障,主要是防止上面的volatile写与下面的可能有的volatile读/写进行重排序。以下图:
从而咱们能够得出,instance = new SingleTon()指令执行顺序图:
因此,咱们能够得出,只要instance被volatile修饰了,2和3就不能重排序。能够得出新的时序图:
这样,咱们经过上面解决方案中第一个方案来保证了线程安全的延迟加载。
public class SingleTon {
privatestatic SingleTon singleTon = null;
publicSingleTon() {
// TODOAuto-generated constructor stub
}
publicstatic SingleTon getInstance(){
if(singleTon == null) {
synchronized(SingleTon.class) {
if(singleTon == null) {
singleTon =new SingleTon();
}
}
}
returnsingleTon;
}
}
考虑这样一种状况,就是有两个线程同时到达,即同时调用getInstance() 方法,
此时因为singleTon== null ,因此很明显,两个线程均可以经过第一重的 singleTon== null ,
进入第一重 if语句后,因为存在锁机制,因此会有一个线程进入 lock 语句并进入第二重 singleTon== null ,
而另外的一个线程则会在lock 语句的外面等待。
而当第一个线程执行完new SingleTon()语句后,便会退出锁定区域,此时,第二个线程即可以进入lock 语句块,
此时,若是没有第二重singleTon== null 的话,那么第二个线程仍是能够调用 new SingleTon()语句,
这样第二个线程也会建立一个SingleTon实例,这样也仍是违背了单例模式的初衷的,
因此这里必需要使用双重检查锁定。
细心的朋友必定会发现,若是我去掉第一重singleton == null ,程序仍是能够在多线程下无缺的运行的,
考虑在没有第一重singleton == null 的状况下,
当有两个线程同时到达,此时,因为lock 机制的存在,第一个线程会进入 lock 语句块,而且能够顺利执行 new SingleTon(),
当第一个线程退出lock 语句块时, singleTon 这个静态变量已不为 null 了,因此当第二个线程进入 lock 时,
仍是会被第二重singleton == null 挡在外面,而没法执行 new Singleton(),
因此在没有第一重singleton == null 的状况下,也是能够实现单例模式的?那么为何须要第一重 singleton == null呢?
这里就涉及一个性能问题了,由于对于单例模式的话,newSingleTon()只须要执行一次就 OK 了,
而若是没有第一重singleTon == null 的话,每一次有线程进入getInstance()时,均会执行锁定操做来实现线程同步,
这是很是耗费性能的,而若是我加上第一重singleTon == null 的话,
那么就只有在第一次,也就是singleTton ==null 成立时的状况下执行一次锁定以实现线程同步,
而之后的话,便只要直接返回Singleton 实例就 OK 了而根本无需再进入 lock语句块了,这样就能够解决由线程同步带来的性能问题了。