多线程基础

一、线程运行状态

Java内存模型(JMM)是围绕着多线程的原子性、可见性、有序性来建立的。

原子性:是指一个操作是不可中断的。java内存模型直接保证的原子性操作包括read、load、assign、use、store、write,synchronized块之间的操作也具备原子性。

可见性:是指当一个线程修改了某一个共享变量的值,其他线程是能立即知道这个修改。volatile、synchronized和final能够实现可见性。普通变量与volatile变量的区别是,volatile保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。final关键字的可见性是指:被final修饰的字段在构造函数中一旦初始化完成,并且构造器没有把“this”的引用传递出去,那在其他线程中就能看见final字段的值。

有序性:有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。volatile、synchronized能保证线程之间的有序性。

二、Thread类方法解析

2.1 start()方法(不是run()方法)

start()方法会新建一个线程并让这个线程执行run()方法。

2.2 stop()方法

一般来说,线程执行完毕后就会结束。stop方法会直接终止一个线程,并且会立即释放这个线程所持有的锁。太过于暴力,可能会引起数据不一致的问题,不建议使用。

2.3 sleep(long millis)

让线程休眠多少毫秒,让出CPU资源。当休眠结束后,不一定立即获取CPU执行,是由阻塞进入就绪,重新竞争CPU。

注意:当线程休眠时,如果调用interrupt()方法会产生一个中断异常InterruptedException。

2.4 线程中断 interrupt()

线程中断并不会使线程立即退出,而是给线程发送一个通知,告知线程可以退出了,至于线程怎么处理,完全有线程决定。若设置了中断标志,但是线程没有判断中断标志并处理,线程也不会退出。

interrupt()  //设置线程中断标志

isInterrupted() // 判断是否被中断

interrupted() // 判断是否被中断,并清除中断标志

public static void main(String[] args) {
		
		Runnable target = new Runnable() {	
			@Override
			public void run() {
				while(true){
					System.out.println(Thread.currentThread().getName()+" runing ");
					if(Thread.currentThread().isInterrupted()){
						System.out.println("当前线程被设置的中断标志,手动结束线程");
						break;
					}
					try {
						Thread.sleep(2000);
						//if(出现某种错误){
							//当出现某种错误,需要当前线程退出时,可以设置线程中断
							//Thread.currentThread().interrupt();
						//}
						System.out.println("线程是否设置中断标志:"+Thread.currentThread().isInterrupted());
						
					} catch (InterruptedException e) {
						System.out.println("在休眠的时候中断操作,抛出中断异常,线程是否设置中断标志:"+Thread.currentThread().isInterrupted());
						Thread.currentThread().interrupt();
					}
				}
			}
		};
		Thread t1 = new Thread(target);
		t1.start();
		
	}

2.5 等待(wait)和通知(notify)

这两个方法不是Thread类中的,是Object类中的。

如果一个线程调用了object.wait(),那么它就进入object对象的等待队列,等待被唤醒。在等待队列中,可能会有多个线程,当object.notify()被调用时,会随机选择一个线程,将其唤醒,notifyAll()方法会唤醒所有的线程。

注意:wait和sleep一样,在wait时发生中断,也会抛出中断异常

2.6 挂起(suspend)和继续执行(resume)

被废弃,不推荐使用。因为suspend在导致线程暂停的同时,并不会去释放任何资源。其他线程想要访问被它暂用的锁时,导致无法继续运行。直到进行了resume操作,才能继续。可以使用wait和notify实现其功能。

2.7 等待线程结束(jion)和谦让(yield)

join()

jion(long millis)

yield() 方法会让当前线程让出CPU,进入就绪状态,但还会进行CPU的争夺。

第一个jion表示无限等待,它会一直阻塞当前线程,直到目标线程(谁调用jion谁是目标线程)执行完毕。第二个join给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会继续往下执行。

Thread t0 = new Thread(()->{
  for(int i=0;i<100;i++){
    System.out.println(Thread.currentThread().getName()+"执行");
  }		
});
Thread t1 = new Thread(()->{
  for(int i=0;i<100;i++){
    System.out.println(Thread.currentThread().getName()+"执行");
  }
});
t0.start();
t1.start();
try {
  t0.join();
  t1.join();
} catch (InterruptedException e) {			
  e.printStackTrace();
}
System.out.println("主线程执行");

当没有执行t0.jion()和t1.jion()结果如下:

可以看到主线程抢到CPU先执行,然后t0和t1交替执行。

当执行t0.jion()和t1.jion()结果如下:

可以看到是t0和t1先交替执行,然后主线程再执行,所以当目标线程加上join后,主线程会等目标线程执行结束后再执行。

注意:当仅仅执行t0.jion()时,效果也是这样,join阻塞的是当前线程,其他线程不干扰。其原理是目标线程让当前线程(只是当前线程)进行wait()等待,当目标线程执行完成后,被等待的线程会在退出前调用notifyAll()唤醒所有线程。

2.8 守护线程

守护线程是系统的守护者,在后台默默完成一些服务,当用户线程全部结束后,守护线程也会结束。

Thread t0 = new Thread(target);
t0.setDaemon(true);
t0.start();
// 主线程的操作 ...

当前线程只有用户线程(主线程)和t0,t0设置为守护线程,当主线程执行结束后,t0也会结束;若不设置的话,t0会一直执行。

原文地址:https://www.cnblogs.com/wwzyy/p/10090992.html