进程与线程
进程:是操作系统的基础;是一次程序的执行;是一个程序及数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合运动的过程;它是系统进行资源分配的一个独立单位。
线程:线程可以理解成是在进程中独立运行的子任务,比如:QQ.exe运行时就有很多的子任务在同事运行。还有在线视频、下载文件线程、传输数据线程、发送表情线程等;
同步线程:例如系统产生两个任务显示图片以及介绍文字,在同步线程的情况需要显示完图片后才能开始第二个显示文字介绍的任务,任务多了之后页面会很久才能显示完成,用户体验性很差。
异步线程:系统产生多个任务,多个任务同时进行,通过CPU的时间片不定顺序的执行多个任务。从用户体验性上感觉所有任务是同时进行的,因为时间片很小,毫秒级。
Java实现线程的方式:继承Thread类与实现Runnable接口两种方式。
Thread源码我们发现,Thread实际上也是实现Runnable接口,所以两种方式实际上没有区别。只是出于Java中单跟继承的方式,解决继承的场景,Java不支持多继承。
注意:如果多次调用start()方法,则会出现异常Exception in thread "main" java.lang.IllegalTereadStateException。
Thread构造方法:
public Thread();//空构造方法直接可以创建一个新的线程
public Thread(Runnable target);//将一个线程作为新线程的参数,这样做完全可以将一个Thread对象run()方法交由其他对象的线程进行调用。
public Thread(ThreadGroup group, Runnable target);//分配新的Thread对象。
public Thread(String name);//分配新的Thread对象。
public Thread(ThreadGroup group, Runnable target, String name);//分配新的Thread对象,一边讲target作为其运行对象,将指定的name作作为其名称,并作为引用的线程组的一员。
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize);//分配新的Thread对象,一边讲target作为其运行对象,将指定的name作作为其名称,并作为引用的线程组的一员,并指定堆栈大小。
currentThread();方法:方法返回代码端正在被那个线程调用的信息
public class Run1{
public static void main(String[] args){
System.out.print(Thread.currentThread().getName());
}
}
isAlive();方法:判断当前线程是否处于活动状态。线程处于运行或者准备开始运行的状态,就认为线程是"存活"的。
public class Run1{
public static void main(String[] args){
System.out.print("run" + this.isAlive());
}
}
sleep();方法:本方法是属于Thread,作用是制定的毫秒数内让当前线程"正在执行的线程"休眠(暂停执行)。这个"正在执行线程"是指this.currentThread()返回的线程。不会释放锁,但是会让出CPU。他的监控状态依然保持,当指定的时间到了优又会自动恢复运行状态
wait();方法:wait();是属于Object类线程放弃线程锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本本线程才进入对象锁定吃准备。
getId();//方法是获得线程的唯一标识。
停止线程:停止线程是在多线程中很重要的技术点。停止一个线程以为着在线程处理完成任务之前停止掉正在做的操作,也就是放弃当前的操作。
1.使用退出标志,是线程正常退出,也就是当run方法完成后线程终止。
2.使用stop方法强行终止线程,但是不推荐这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
3.使用interrupt方法中断线程。但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程停止。调用interrupt()方法仅仅是在当前线程打了一个停止标志,并不是真的直接停止这个线程。
判断当前线程是否是停止状态:
Java SDK中Thread.java类里面提供了两种方法:
1.this.interrupted();测试当前线程是否已经停止。(此方法是静态方法)
测试当前线程是否已中断。线程的中断是有该方法清除的。换句话说,如果连续两次调用改方法,则第二次返回false(在第一次调用已经清除了其中的状态之后,切第二次调用检查完中断状态前,当前线程再再次中断的情况除外),官方文档解释的很详细,interrupt()方法具有清除状态的功能,所以第二次调用interrupt()方法返回值是false。
2.this.isInterrupted();测试线程是否已经中断。但不清除状态标志。
thread.interrupt();调用后线程停止标志位更改后,线程通过this.isInterrupted();判断线程是否停止,获得停止标志位的时候并不能停止当前线程,一下代码会继续执行,可以通过抛出异常的方式停止代码执行。
在沉睡中停止:
1.先进入sleep()状态后调用interrupt();,sleep状态代买会抛出异常。
2.先调用停止后进入sleep();会执行代码后抛出异常。
暴力停止:使用stop()方法停止线程是非常暴力的。(资源释放、资源同步)
调用stop()方法时会抛出java.lang.ThreadMethod异常,但在通常情况下,次异常不需要明显地捕捉。stop()方法已经作废,因为如果强制让线程停止则有可能使一些清理工作的工作得不到完成。另外一个情况就是对锁定的对象进行了"解锁",导致数据的不到同步处理,出现数据不同步问题。由于stop()在JDK中被表明是"过期/作废"的方法,显然它在功能上具有缺陷,所以不建议在程序中使用stop()方法。
使用return停止线程:
将方法interrupt()与return结合使用也能实现体质线程的效果。
public void run(){
if( this.isInterrupted) {
return;
}
System.out.println("执行打印语句,线程没有停止");
}
不过还是建议使用“抛异常”法来实现线程的停止,因为在cache块中可以对异常信息进行相关的处理,而且使用异常流能更好、更方便地控制程序的运行流程,不至于代码中出现喝多的return;造成污染。
暂停线程:
暂停线程意味着次线程还可以恢复运行,在Java多线程中,可以使用suspend()的方法暂停线程,使用resume()方法恢复线程的执行。
暂停独占:在使用suspend()月resume()方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他线程五大访问公共同步对象。例如:当一个线程调用对象的具有同步功能的方法,方法内调用了Thread.currentThread.suspend();后没有使用resume()。其他线程将无法调用此方法,因为这个方法被之前的线程永远的暂停了。
suspend();方法使用不当会出现数据同步问题。
yieid();方法的作用是放弃当前CPU资源,将它让给其他的任务去占用CPU运行时间。但放弃的时间不确定,有可能刚刚放弃,马上有获取了CPU时间片。
线程的优先级:
在操作系统中,线程可以划分优先级,优先级较高的线程获得到的CPU资源越多,也就是CPU优先执行优先级较高的线程对象任务。设置优先级有助于帮助“线程规划器”确定下一次选择哪一个线程优先执行。
Java中,线程优先级分为1~10个等级,如果小于1或者大于10,则JDK抛异常throw new IllegelArgumentException()。
线程优先级的集成特性:
在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A线程是一样的。getPriority();方法后去线程优先级方法,setPriority();设置优先级方法。
优先级具有规则性:高优先级的线程总是大部分先执行完,但是不代表高优先级的线程全部先执行完。
优先级具有随机性:前面介绍了线程的有衔接比较高则先执行完run()方法的任务,但是这个结果不能说的太肯定,因为线程优先级具有“随机性”,也就是优先级高的线程不一定每一次都先执行完成。
守护线程:1.用户线程。2守护线程Daemon。
守护线程:任何一个守护线程都是这个JVM中所有非守护线程的保姆,只要当前JVM实例中存在非守护线程切没有结束,那么守护线程就要工作,只有当最后一个非守护线程结束,则守护线程随着JVM一起结束工作(最典型的守护线程及时GC)。
对象及变量的并发访问
synchronized同步方法:
非线程安全:在多个线程对同一个对象的实例变量进行并发访问时发生,产生后果就是“脏读”也就是获取到的数据其实是被更改过的。
线程安全:获取的实例变量的值是经过同步处理的,不会出现脏读的现象。
方法内部的变量为线程安全
“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是线程安全的了。
实例变量非线程安全:如果两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则会可能出现“非线程安全问题”,只需在方法前添加synchronized即可。
synchronized方法与锁对象:
两个线程同时调用一个对象的同一个函数运行过程是通过CPU时间片的分发决定的。两个线程同时调用同一个对象的同一个使用synchronize函数运行的顺序一定排队进行的。
只有共享资源访问才需要同步,如果不是共享资源,那么根本就没有同步的必要。
1)A线程先持有了object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的函数。
2)A线程先持有了object对象的Lock锁,B线程如果在这个时候调用object对象中的synchronized类型的方法则需要等待,也就是同步(释放对象所后才能调用)。
脏读:虽然在赋值的时候进行了同步,但在取值的时候有可能出现一些意想不到的意外,这种情况就是脏读。发生脏读的情况是在读取实例变量的时,此值已经被其他线程改变了。
synchronized锁重入:
关键字synchnorized拥有锁冲入的功能,也就是在使用synchnorized时,当一个线程得到一个对象锁后,再次请求对象时可以再次得到该对象的锁的。这样证明在一个synchonrozed方法/块的内部调用本类的其他synchronized方法/块时是永远可以得到锁的。
“可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1个线程获得了某个对象的锁。此时这个对象锁还没有释放,当其再次想获得这个对象锁的时候是可以获取的,如果不可重入锁的话,就会造成死锁。
锁重用是可以继承的。当存在父子类继承关系时,子类是完全可以通过“锁重入”调用父类同步方法的。
出现异常,锁自动释放:
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
线程同步不具有继承性:
同步不可继承,一个类的带有synchronized函数,再被继承时候这个函数被重写不具有同步功能。同步不能继承,所以还得再子类的方法中添加synchronized关键字。
synchronized同步语句块:
用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待较长的时间。再这样的情况下可以使用synchronized同步语句块来解决。synchronized方法是对象进行加锁,而synchnorized代码块是对象进行加锁。
当两个并发线程访问同一个对象object中的synchronized同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块后才能执行该代码块。
synchronized代码块只同步部分重要代码,一个函数部分是同步部分是异步执行的,这样即使保证了代码同步有保证了时间效率。
在使用同步synchronized(this)代码块需要注意的是,当一个线程访问object的synchronized(this)同步代码时,其他线程对一同一个object中所有其他synchronized(this)同同步代码块访问被阻塞,这说明synchronized使用的“对象监听器”是一个。
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
将任意对象作为对象监听器:
多个线程调用同一个对象不同名称的synchronized同步方法或者synchronized(this)同步代码块时,调用的效果就是顺序执行,也就是同步的,阻塞的。
说明synchronized同步方法或者synchronized(this)同步代码块分别有两种作用。
(1)synchronized同步方法。
1)对其他synchronized同步方法或者synchronized(this)同步代码块调用呈阻塞状态。
2)同一时间只能有一个线程可以执行synchronized同步方法中的代码。
(2)synchronized(this)同步代码块
1)对其他synchronized同步犯法后者synchronized(this)同步代码块调用呈阻塞状态。
2)同一时间只有一个线程可以执行synchronized(this)
使用synchronized(this)格式来同步代码,其实Java还支持对“任意对象”作为“对象监听器”来实现同步的功能。这个“任意对象”大多数是实例变量以及方法的参数,使用格式为synchronized(非this对象)。
synchronized(非this对象)格式的作用只有1种:synchronized(非this对象)
1)在多个线程持有“对象监听器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
2)当持有“对象监听器”为同一个对象的前提条件下。同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
锁非this对象具有一个优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象。则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可以大大提高运行效率。
使用“synchronized(非this对象x)同步代码块”,格式进行同步操作时,对象监听器必须是同一个对象。如果不是同一个对象监听器,运行的结果就是异步调用了,就会交叉运行。(synchronized或者synchronized(this/非this对象x) 同步过程中重要的是锁到哪一个对象,锁在同一个对象上是同步,锁在不是同一个对象异步的)。由于对象监听器不同,所以运行结果就是异步的。
同步代码块放在非同步synvhronized方法中进行声明,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序同步的,这样极易出现“脏读”问题。
synchronized(非this对象x)格式的写发是将X对象本身作为"对象监听器“,就这样可以得出三个结论:
1)当多个线程同时执行synchronized(x){}同步代码块时呈现同步效果。
2)当其他线程执行X对象中的synchronized同步方法时呈现同步效果。
3)当其他线程执行X对象方法里面的synchronized(this)代码块时也呈现同步效果。
但要注意的是:如果其他线程调用不加synchronized关键字的方法时,还是异步调用。
静态synchronized方法与synchronized(class)代码块
synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是个对象枷锁。
线程A调用object static synchronized 方法,并且B现线程同时调用object synchronized方法,是异步调用的,因为 s sy 是使用的类锁,s用的对象锁。使用的不是一个锁所以出现异步现象。
同步synchronized(class)代码块的作用其实和synchronized static方法作用是一样的。
public static synchronized p(){}
public static p(){ synchronized(p.class){}}
数据类型String常量池功能
String s = "adb" ;//放在常量池
String s2 = "abc";//放在常量池
String s1 = new String(s); //s1存放在堆里。
System.out.plent("是否相同” +s = s2); //是否相同true;
synchronized分别对s 与s2做同步 ,线程A访问s,同事线程B访问s2,实际运行结果s2访问结束后才运行,说明s与s2是同步的。
多线程死锁:
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放锁的。
锁对象变更:
将任何数据作为同步锁,需要注意的是,是否多个线程同时保持对象锁,如果同时持有相同的锁对象,则这些线程间就是同步的;如果分别获得对象锁,这些线程之间就是异步的。
private String lock = "123";
public void testMesthod(){
synchronized(lock){
......
lock = "456";
}
}
Thead t1 ;Thread t2; t1.start Thread.sleep(); t2.start();
在其他线程访问lock的时候,lock已经改变了。所以不会出现数据同步问题,因为多个线程持有的锁改变了。
Thead t1 ;Thread t2; t2.start();
线程A和B只有的锁都是“123”。虽然将锁改变成了“456”。但是运行结果还是同步的,因为A和B共同争抢的锁都是“123”。
volatile关键字:
关键字volatile的作用是强制从公共栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。例如:private boolean isRuning = true;存在于公共栈与私有栈中。在JVM被设置为-sever模式时为了线程运行的效率,线程一直在私有堆栈中获取isRuning的值。而代码设置值虽然被执行了,更新的却是公共栈中的值,所以程序使用的都是旧值。
使用volatile关键字增加了实例变量在多个线程之间的可见性。但volatile关键字最致命的缺点是不支持原子性。
下面将关键字synchronized和volatile进行一下比较:
1)关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上得到了很大的提升,在开发中使用synchronized比率还是比较大的。
2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
3)volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也就可以间接的保证可见性,因为他会将私有内存和公共内存的数据做同步。
4)再重申一下,关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
volatile非原子的特性:
关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。
线程间通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必要方案之一,可以说,使用线程通讯后,系统之间的交互性会更强大,在大大提高了CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把握与监督。