如何正确停止线程,守护线程,volatile关键字与synchronized关键字详解(使用例子详解这三个概念之间的关系)

2022-07-26 12:48:19

一,从jdk1.2开始,以下几个关于线程的方法就不提倡使用了:

1,停止线程:public void stop();

2,销毁线程:public void destroy();

3,挂起线程,使线程暂停执行:public final void suspend();

4,恢复挂起的线程:public final void resume();

之所以禁止使用这些方法,使用为这些方法可能导致线程死锁。所以在停止线程的时候应该通过一种柔和的方式来处理,下面就是现在最常用的停止线程的方式:

public class TestDemo{
	public static boolean flag=true;
	public static void main(String args[]) {
		new Thread(new Runnable() {
			public void run() {
				int num=0;
				while(flag) {
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"---"+num++);
				}
			}
		},"执行线程").start();
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		flag=false;
	}
}

上面代码实现的逻辑是:让主线程休眠200ms,在主线程休眠200ms期间,子线程(“执行线程”)每隔50ms打印一句话,等子线程打印四次,也即是主线程休眠到了200ms,让flag=false;于是子线程就不再执行了。

这就好像飞机正在飞,如果使用stop,就相当于让飞机发动机立即停止,这样肯定不行,会造成非常大的安全隐患,但是使用flag标志来停止线程就比较缓和。

二:守护线程:

如果主线程或者其他子线程还在存活的时候,那么守护线程就会一直存在并且运行在后台,这就相当于一个人有一个保镖,只有当这个人活着的时候才起到保护作用,如果这个人死了,保镖也就不起作用了。下面看两个方法:

public final void set setDaemon(boolean values)把一个线程设置为守护线程。

public fianl boolen isDaemon()判断该线程是否为守护线程

public class TestDemo{
	public static void main(String args[]) {
		Thread userThread=new Thread(new Runnable() {
			public void run() {
				for (int i=0;i<10;i++) {
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("User线程---"+i);
				}
			}
		},"用户线程");
		Thread daemonThread=new Thread(new Runnable() {
			public void run() {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				for (int i=0;i<Integer.MAX_VALUE;i++) {
					System.out.println("Daemon线程---"+i);
				}
			}
		},"守护线程");
		//daemonThread.setDaemon(true);
		userThread.start();
		daemonThread.start();
	}
}

上面        //daemonThread.setDaemon(true);把这句话注释掉了,意思是没有把daemonThread设置为守护线程,那么用户线程执行10次之后,daemonThread线程还将继续执行,一直执行到Integer.MAX_VALUE为止。

现在        daemonThread.setDaemon(true);取消这句话的注释,那么daemonThread会随着用户线程的停止而停止(当然会有延迟)。

可以发现守护线程总是围绕在用户线程的周围,程序执行完毕了,守护线程也就消失了,在整个JVM中,最大的守护线程也就是GC线程了,程序执行中GC线程将一直存在,程序执行完毕,GC线程业绩跟着消失。

三,volatile关键字详解

volatile关键字主要是在属性定的时候使用,表示此属性为直接数值操作,而不进行副本的拷贝处理。但是一些书上将其理解为同步属性,这是不对的。下面来看一个关于线程同步的文章:

java同步与死锁

class MyThread extends Thread{
	//加上volatile关键字表示进行直接内存操作
	private volatile int ticket=5;
	public void run() {
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		while(ticket>0) {
			System.out.println(Thread.currentThread().getName()+"卖的"+ticket--);
		}
	}
}
public class TestDemo{
	public static void main(String args[]) {
		MyThread myThread=new MyThread();
		new Thread(myThread,"窗口A").start();
		new Thread(myThread,"窗口B").start();
		new Thread(myThread,"窗口C").start();
	}
}

虽然属性已经加上了volatile关键字,但是这并没有把解决线程同步问题。加上synchronized关键字之后才解决了线程同步问题,如下:这才是volatile关键字的正确使用方法。

class MyThread extends Thread{
	//加上volatile关键字表示进行直接内存操作
	private volatile int ticket=5;
	public synchronized void run() {
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		while(ticket>0) {
			System.out.println(Thread.currentThread().getName()+"卖的"+ticket--);
		}
	}
}
public class TestDemo{
	public static void main(String args[]) {
		MyThread myThread=new MyThread();
		new Thread(myThread,"窗口A").start();
		new Thread(myThread,"窗口B").start();
		new Thread(myThread,"窗口C").start();
	}
}

从中可以知道线程的同步只能使用synchronized关键字,而volatile关键字并不能实现同步。

在正常进行变量处理的时候往往会经过以下几个步骤:

1,获取变量原有的数据内容副本

2,利用副本为变量进行数学计算

3,将计算后的变量保存到原始空间之中

而一个属性上使用了volatile关键字,表示不使用副本而直接使用原始变量。这就相当于节约了拷贝时间,重新保存的步骤。

下面写一个例子来证明Volatile关键字的线程可见性:(保证线程可见性)


public class VolatileTest {
    public static void main(String args[]){
        TestRunnable testRunnable=new TestRunnable();
        new Thread(testRunnable).start();
        while (true){
            if (testRunnable.isFlag()){
                System.out.println("----------");
                break;
            }
        }
    }

}


class TestRunnable implements Runnable{
    private volatile boolean isFlag=false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isFlag=true;
        System.out.println("isFlag=="+isFlag);
    }

    public boolean isFlag() {
        return isFlag;
    }
}

1,开辟一块内存

2,初始化对象

3,给变量赋值(指向内存地址)

但是有时候会出现1,3,2的情况,这就是重排序,为了防止重排序,就要再变量前边加上volatile关键字

保证线程可见性

volatile与synchronized的区别:
1,volatile主要在属性上使用,而synchronized主要在代码块或者方法上使用。

2,volatile无法描述同步处理,它只是描述直接内存的处理,避免了副本的操作,而Synchronized是实现同步的。

  • 作者:北京流浪狗
  • 原文链接:https://blog.csdn.net/yaoyaoyao_123/article/details/89150684
    更新时间:2022-07-26 12:48:19