多进程:操作系统中同时运行的多个程序。
多线程:线程是程序执行的一条路径,在同一个进程中可以包含多条线程。
造成的原因:CPU在瞬间不断切换去处理各个线程而导致的,可以理解成多个线程在抢CPU资源。
1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。
2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的。
1.创建线程的两种方式
继承Thread类:1.定义一个类继承Thread类
2.目的是让子类覆盖Thread类中的run()方法,将要让线程运行的代码都封装到run()方法中
3.创建Thread的子类对象,就是创建线程对象。
4.调用start()方法开启线程并让线程执行,同时还会告诉JVM去调用run()方法
实现Runnable接口:
1.定义一个类实现Runnable接口
2.覆盖接口中的run()方法,将要让线程运行的代码都封装到run()方法中
3.创建Thread类对象,将Runnable接口的子类对象作为参数传递给Thread类的构造方法,因为要让线程对象明确要运行的是Runnable接口里的run()方法。
4.调用Thread对象的start()方法开启线程,内部会自动调用Runnable的run()方法。
两种方式的区别:
源码上的区别:
1.继承Thread:由于子类重写了Thread类的run(),当调用start()时,直接找子类的run()方法。
2.实现Runnable:构造函数中传入了Runnable的引用,成员变量记住了它,start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法。
继承Thread:可以直接使用Thread类中的方法,代码简单,但是如果已经有了父类,就不能用这种方法。
实现Runnable:因为接口是可以多实现的,即使有了父类也没关系,因为有了父类也可以实现接口。
一般用第二种,实现Runnable接口避免了单继承的局限性,而且更加符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
线程状态:
Thread类内部有个public的枚举Thread.State,里边将线程的状态分为:
1.新建(NEW):start():至今尚未启动的线程处于这种状态。
2.运行(RUNNABLE):具备执行资格,同时具备执行权;正在 Java 虚拟机中执行的线程处于这种状态。
3.冻结(WAITING):sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格。
4.阻塞状态(BLOCKED):线程具备cpu的执行资格,没有cpu的执行权。
5.等待状态(TIMED_WAITING):等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
6.消亡(TERMINATED):stop()或者run()方法结束,已退出的线程处于这种状态。
2.线程安全问题
发现一个线程在执行多条语句并运算同一个数据时,还没有执行完,就被其他线程执行了。原因:1.多个线程在操作共享数据
2.有多条语句对共享数据进行运算。
解决思路:只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,不要让其它线程参与运算就行了。
2.1同步代码块
格式:synchronized(对象) { //任意对象都可以,这个对象就是锁。
需要被同步的代码;
}
前提:必须要有多个线程,并使用同一个锁。
好处:解决了线程安全问题。
弊端:因为每个线程都去判断同步锁,会消耗资源,降低了程序的运行效率。
2.2同步函数
将同步关键字synchronized定义在函数上,让函数具备了同步性,使用的锁是this锁。当同步函数被static修饰时,同步使用的锁是该类的字节码文件对象:类名.class
原理:静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。所以静态加载时,只有一个对象存在,那么静态同步函数就使用这个对象。
同步代码块和同步函数的区别:
同步代码块:使用的锁可以是任意对象,如果有多同步,必须使用同步代码块,来确定不同的锁。
同步函数:使用的锁固定是this,线程任务只需一个同步时,完全可以使用同步函数。
静态同步函数:不使用this锁,而是该函数所属类的字节码文件对象,可以使用getClass()获取,也可以用当前类名.class表示。
2.3同步死锁
通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数,然后这两种同步互相在争夺一把锁,从而造成程序假死不动,称之为死锁。避免死锁的一个通用的经验法则是:尽量不要嵌套使用,当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。
3.线程间通信
多个线程在操作同一个资源,但是操作的动作不同。解决方案:1:将资源封装成对象。
2:将线程执行的任务(任务其实就是run方法。)也封装成对象。
3.1等待唤醒机制
涉及的方法:wait():将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify():唤醒线程池中某一个等待线程。
notifyAll():唤醒的是线程池中的所有线程。
注意:1.这些方法都需要定义在同步中,因为这些方法必须要标识所属的锁。
例:你要知道A锁上的线程被wait了,那这个线程就处于A锁的线程池中,只能被A锁的notify唤醒。
2.这三个方法都定义在Object类中。为什么?
原因:因为这三个方法都需要定义同步内,并标识所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。
wait和sleep区别:
从执行权上来分析:
wait:可以指定时间也可以不指定时间;如果不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到线程自动从冻结状态转成运行状态(临时阻塞状态)。
从锁上来分析:
wait:必须定义在同步中,同步中线程执行到wait,线程会释放执行权,而且会释放锁。
sleep:可以不定义在同步中,同步中线程执行到sleep,线程会释放执行权,但是不释放锁。
3.2 JDK1.5新同步锁
JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作。
Lock接口:替代了同步代码块或者同步函数,将同步的隐式锁操作变成显示的锁操作,同时更灵活,可以在一个锁上加上多组监视器。
Condition接口:await(),signal(),signalAll() 替代了 Object中的wait(),notify(),notifyAll()方法,将这些监视器方法单独进行了封装,变成了Condition监视器对象,可以和任意锁进行组合。
4.线程的其它用法
停止线程:第一种方式:stop()方法,有安全隐患,已过时。
第二种方式:结束run方法,定义循环的结束标记,控制住循环。
第三种方式:通过Thread类中的interrupt()中断线程方法,将线程冻结状态强制清除,让线程恢复到运行状态(让线程重新具备执行资格),因为是强制性的所以会发生InterruptedException异常,可以在catch中捕获异常,在异常处理中改变控制循环的标记,让循环结束,然后run()结束。
守护线程(后台线程):
setDaemon():设置一个线程为守护线程,该线程不会单独执行,处于后台运行,任务是为其他线程提供服务,JVM的垃圾回收就是典型的后台线程。
特点:当所有的前台线程都结束,后台线程自动退出。
setDaemon(true)必须在start()调用前,否则出现IllegalThreadStateException异常。
前台线程创建的线程默认是前台线程。
线程插队(加入线程,控制线程,联合线程):
join():临时加入一个线程,当前线程暂停,等待指定的线程执行结束后,当前线程再继续。
join(int):可以等待指定的毫秒之后继续。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。
线程礼让:
yield():暂停当前正在执行的线程对象,并执行其他线程。
Thread的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态;所以完全有可能某个线程调用了yield()之后,线程调度器又把他调度出来重新执行。
线程优先级:
每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。
并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度。
Thread对象的setPriority(int x)和getPriority()来设置和获得优先级。
MAX_PRIORITY:值是10
MIN_PRIORITY:值是1
NORM_PRIORITY:值是5(主方法默认优先级)