多线程学习笔记六 - 线程的消亡以及一些常用方法的介绍
多线程wait()和sleep()的区别:
1、wait()可以指定时间,sleep()必须指定时间。
2、在同步中时,对cpu的执行权和处理不同。
wait() 释放执行权,释放锁。
sleep() 释放执行权,不释放锁。
在同步锁中(synchronized),多线程可能同时进入锁中(多生产者多消费者问题),都被wait(),此时这些线程都释放了执行权并且释放了锁。
当有notifyAll()将其唤醒的时候,这些线程都获得了执行权,只有当执行notifyAll()的线程执行完同步锁内的内容的时候才会释放锁,其他线程只有得到锁以后才会执行。
线程的消亡:
停止线程:
1、stop();-->已过时, 该方法具有固有的不安全性。
2、void suspend()--> 已过时。 该方法已经遭到反对,因为它具有固有的死锁倾向。
3、多线程要写循环的原因:因为很多任务要执行很长时间,所以需要开辟一个线程单独执行。
思路:也就是说线程里面都会有循环,只要控制住循环就能结束任务。如果控制循环呢??控制循环常用的思路就是定于标记来完成的。
下面介绍一个小demo:
public class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while(true){
if(++num == 50){
st.setFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"============");
}
}
}
class StopThread implements Runnable{
private boolean flag = true;
public void setFlag() {
flag = false;
}
@Override
public void run() {
while(flag){
System.out.println(Thread.currentThread().getName()+"*************");
}
}
}
虽然这种方式很常用,但是这样的方法会有解决不了的问题:
如果线程处于冻结状态,执行不到setFlag()方法就挂掉了。
例如:
public class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while(true){
if(++num == 50){
st.setFlag();
break;
}
System.out.println(Thread.currentThread().getName());
}
}
}
class StopThread implements Runnable{
private boolean flag = true;
public void setFlag() {
flag = false;
}
@Override
public void run() {
synchronized(this){
while(flag){
try {
System.out.println(Thread.currentThread().getName());
wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"******"+e);
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"*************");
}
}
}
4、 void interrupt() 中断线程。
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,
或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,
则其【中断状态将被清除】,它还将收到一个 InterruptedException。
我们来看一个示例更容易理解一些:
public class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while(true){
if(++num == 5){
//st.setFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName());
}
}
}
class StopThread implements Runnable{
private boolean flag = true;
public void setFlag() {
flag = false;
}
@Override
public void run() {
synchronized(this){
while(flag){
try {
wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"******"+e);
//flag = false;
}
}
System.out.println(Thread.currentThread().getName()+"*************");
}
}
}
运行结果
main main main main Thread-0******java.lang.InterruptedException Thread-1******java.lang.InterruptedException
分析:当两个支线程在运行的时候因为执行到了wait()方法,所以进入线程池等待,当主线程执行到t1.interrupt();t2.interrupt();这两句代码的时候,
两个支线程的等待被清除,并抛出了一个异常 InterruptedException。
虽然抛出了异常,但是程序并没有终止,而是继续循环,当进入循环的时候又被wait();此时程序便卡在这里。
此时想要终止线程,在catch中加入flag = false;即可。
综上:可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态,让线程具备CPU的执行资格。但是因为是强制动作,所以会抛异常,要记得处理。
守护线程:
void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出(JVM退出意味着只有守护线程的时候,守护线程自动结束)。
该方法必须在启动线程前调用。
守护线程示例:
public class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
MyDemo md = new MyDemo();
Thread t1 = new Thread(st);
Thread t2 = new Thread(md);
t1.start();
t2.setDaemon(true);
t2.start();
int num = 0;
while(true){
if(++num == 5){
t1.interrupt();
break;
}
System.out.println(Thread.currentThread().getName());
}
}
}
class StopThread implements Runnable{
private boolean flag = true;
public void setFlag() {
flag = false;
}
@Override
public void run() {
synchronized(this){
while(flag){
try {
wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"******"+e);
flag = false;
}
}
System.out.println(Thread.currentThread().getName()+"*************");
}
}
}
class MyDemo implements Runnable{
int count =0 ;
@Override
public void run() {
while(true){
System.out.println(Thread.currentThread().getName()+" "+count);
count++;
}
}
}
运行结果:
main Thread-1 0 main Thread-1 1 main Thread-1 2 main Thread-1 3 Thread-1 4 Thread-1 5 Thread-0******java.lang.InterruptedException Thread-1 6 Thread-0************* Thread-1 7 Thread-1 8 Thread-1 9 Thread-1 10 Thread-1 11 Thread-1 12
分析:如果没有守护线程,Thread-1会不停的执行下去,但是因为有Thread-1线程在开启之前就设置了保护线程,所以当主线程和Thread-0结束后,守护线程也结束了。
下面介绍join()方法:
public final void join()throws InterruptedException ----> 等待该线程终止。理解:先让该线程执行,该线程结束后再执行主线程。
示例:
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
MyJoin mj = new MyJoin();
Thread t1 = new Thread(mj);
Thread t2 = new Thread(mj);
t1.start();
t1.join();
t2.start();
for (int i = 0; i < 50; i++) {
System.out.println("==="+Thread.currentThread().getName()+"===");
}
}
}
class MyJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("**********"+Thread.currentThread().getName()+"**********");
}
}
}
(运行结果太多不展示)
第一次结果:以上示例的结果表明不管执行多少次都是Thread-0先执行,当Thread-0结束以后其他线程才能执行。
第二次结果:如果将t1.join();放在t2.start();的下面运行代码,那么执行结果会变得不同。t1和t2是交替执行的,而主线程则在最后执行。
分析:我们可以得出结论,当下线程如果调用了join()方法,那么主函数会让出CPU的执行权和执行资格,直到该线程结束,而对其他线程没有影响。
第一次结果是因为一开始加载还没有启动t2线程,主函数便让出了CPU的执行权和执行资格,所以t2不会出现。
第二次结果是因为t2线程已经启动,即便是主函数让出了CPU的执行权和执行资格,也只是针对于t1而言的,所以不影响t2的运行。
join()的运行场景:需要临时加入一个线程运算时候可以用join()方法。当然join也可以带参数的,使用时查看API即可。
介绍线程优先级:
void setPriority(int newPriority) :更改线程的优先级。
static int MAX_PRIORITY -->10
线程可以具有的最高优先级。
static int MIN_PRIORITY -->1
线程可以具有的最低优先级。
static int NORM_PRIORITY -->5
分配给线程的默认优先级。
线程的优先级共有1-10,为了方便提供了上述静态变量供使用。默认情况下线程的优先级都是5。
注意:线程的优先级只能保证该线程在长时间的多线程任务中获得执行资格后可以多占用一点时间,其实线程还是抢占式的,而不是霸占了执行权和执行资格直到自己执行完。
介绍线程组:
public class ThreadGroup:该类的出现,将线程分组,便于管理线程。
yield()方法:
public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
最后有两道题:
1、下面程序回报什么错?
class Test implements Runnable{
public void run(Thread t){
}
}
2、分析并得出下列代码的结果
ublic class Demo{
public static void main(String[] args){
new Thread(new Runnable(){
public void run(){
System.out.println("Runnable");
}
}){
public void run(){
System.out.println("subThread");
}
}.start();
}
}
如果子类没有重写run()方法呢?
public class Demo{
public static void main(String[] args){
new Thread(new Runnable(){
public void run(){
System.out.println("Runnable");
}
}){
}.start();
}
}
如果也没有任务呢?
public class Demo{
public static void main(String[] args){
new Thread(){
}.start();
}
}
答案:
1、因为Test类实现了Runnable接口,就必须实现它的方法,如果不实现它的方法,那么必须是抽象类,所以用abstract来修饰Test。
2、subThread Runnable 无