线程:线程是进程当中独立运行的子任务。
javaw.exe主要用于启动基于GUI的应用程序。
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就是当前的对象。
Thread.currentThread()可以获取当前线程的引用,一般都是在没有线程对象又需要获得线程信息时通过Thread.currentThread()获取当前代码段所在线程的引用。
https://blog.csdn.net/yezis/article/details/57513130
在指定的毫秒内让当前“正在执行的线程”休眠(暂停执行),这个“正在执行的线程”是指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语句块了,这样就可以解决由线程同步带来的性能问题了。