Java高并发学习(一)

Java高并发学习()

初始线程:线程的基本操作

进行java并发设计的第一步,就是必须了解Java虚拟机提供的线程操作的API。比如如何创建并启动线程,如何终止线程,中断线程等。

1.定义线程:

(1).继承Thread方法,形式如下

	public static class T1 extends Thread{
		@Override
		public void run(){
			System.out.println("fist.T1.run()");
		}
	}

(2) .实现Runnable接口创建线程方式,形式如下

class MyThread implements Runnable{
	@Override
	public void run(){
		System.out.println("fist.T2.run()");
	}
}
public class fist {
	public static void main(String args[]){
		MyThread mythread = new MyThread();
		Thread t1 = new Thread(mythread);
		t1.start();
	} 
}

说明:Thread类有一个非常重要的构造方法public Thread(Runnable target),他传入一个Runnable实例,在start()方法调用时,新的线程会执行Runnable.run()方法。

实际上,默认的Thread.run()就是这么做的:

Public void run(){
   if(target != null){
      Target.run();
   }
}


2.启动线程:

启动线程很简单。只需要使用new关键字创建一个线程对象,并且将它start()起来即可。

Thread t1 = new Thread();
t1.start();

注意一下:

下面的代码能通过编译,也能正常执行。但是,却不能创建一个新的线程,而是在当前线程中调用run()方法,只是作为一个普通方法去调用。

Thread t1 = new Thread();
t1.run();

因此希望大家特别注意,调用start()方法和run()方法的区别。


3.终止线程:

如何正常的关闭一个线程呢?查阅JDK,不难发现Thread提供了一个stop方法。如果使用stop()方法,就可以立即将一个线程终止,非常方便。但是eclipse会提示你stop()方法为废弃方法。也就是说,在将来JDK会删除该方法。

为什么stop()方法会被废弃呢?原因是stop()方法太过暴力,强行把在执行的线程停止,可能会导致数据不一致问题。

那如果需要停止一个线程时,应该怎么做呢?其实方法很简单,只是需要由我们自行决定线程何时退出就可以了。

例如:

class MyThread extends Thread{
	volatile boolean stopme = false;
	public void stopeMe(){
		stopme = true;
	}
	@Override
	public void run(){
		while(true){
			if(stopme){
				System.out.println("exit by stop me");
				break;
			}
		}
	}
}

代码中定义了一个标记变量stopme,用于指示线程是否需要退出。当stopMe()方法被调用,stopme被设置为true,此时,线程会检测到这个改动,线程就自然退出了。


4.线程中断:

从表面上理解,中断就是让线程暂停执行的意思,实际上并非如此。在上一节中我们已经讨论了stop()方法停止线程的害处,并且提供了一套完善线程退出功能。那在JDK中是否提供更强大的支持呢?答案是肯定的,那就是线程中断。

与线程中断有关的有三个方法,这三个方法看起来很像,所以可能会引起混淆和误用,请大家注意。

public void Thread.interrupt()   //中断线程
Public void boolean Thread.isInterrupted()   //判断线程是否被中断
Public static boolean Thread.interrupted()   //判断是否被中断,并请除当前中断状态

Thread.interrupt()设置中断标志位,Thread.isInterrupted()检查中断标志位,Thread.interrupted()清除中断标志位。

下面这段代码对t1进行了中断,那么t1中断后,t1会停止执行吗?

class MyThread extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("Thread running");
		}
	}
}

public class fist{
	public static void main(String args[]){
		Thread t1 = new MyThread();
		t1.start();
		t1.interrupt();
	}
}

在这里虽然会t1进行了中断,但是t1并没处理中断的逻辑,因此,即使t1线程被设置了中断,但是这个中断不会发生任何作用。

如果希望t1在中断后退出,就必须为他增加相应的中断处理代码:

class MyThread extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("Thread running");
			if(Thread.currentThread().isInterrupted()){
				System.out.println("interrput");
				break;
			}
		}
	}
}

public class fist{
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread();
		t1.start();
		Thread.sleep(2000);
		t1.interrupt();
	}
}

特别注意,如果在循环体中出现了wait()或者sleep()这样操作的时候,中断可能会被忽略。

Thread.sleep()方法会让当前线程休眠若干时间,他会抛出一个interruptException中断异常。interruptException不是运行时异常,也就是程序必须捕获并处理他。当线程在休眠时,如果被中断,这个异常就会产生。

class MyThread extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("Thread running");
			if(Thread.currentThread().isInterrupted()){
				System.out.println("interrput");
				break;
			}
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				System.out.println("when sleep interrupt");
				Thread.currentThread().interrupt();
			}
			System.out.println("Thread end");
		}
	}
}

public class fist{
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread();
		t1.start();
		Thread.sleep(1000);
		t1.interrupt();
	}
}

如果线程运行到了sleep()代码段,主程序中断线程,线程这这时候抛出异常,进入catch的异常处理。在catch代码段中,由于捕获到了中断,我们可以立即退出线程。在这里我们并没有这么做,因为也许在这段代码中,我们还必须进行后续处理,保证数据的一致性和完整性,因此,执行了Thread.interrupt()方法在次中断自己,设置中断标志位。只有这么做才能当线程休眠时响应中断。

注意:Thread.sleep()方法由于中断而抛出异常,此时,它会清除中断标志位,如果不加处理,那么在下一次循环开始前,就无法捕获这个中断,故在异常处理中,在次设置中断标记位。


5.等待(wait)和通知(notify)

为了支持多线程之间的协作,JDK提供了两个非常重要的接口,线程等待wait()方法和线程通知方法notify()。这两个方法不是在Thread类中的,而是Object类的。这也意味着任何对象都能调用者这两种方法。

public final void wait() throws InterruptException
public final native void notify()

wait()notify()是怎么工作的呢?如果一个线程调用了object.wait()方法,那么他就会进入object对象的等待队列。这个队列中可能会有多个等待线程,因为系统运行同时等待某个对象。当object.notify()被调用时,他就会从这个等待队列中随机选择一个线程唤醒。这里希望大家注意,这个唤醒过程是不公平的,并不是先等待的线程会优先选择。

除了object.notify()之外还有object.notifyAll()方法,他会唤醒等待队列中的所有线程。

这里还需要注意一点,object.wait()方法和object.notify()方法并不是可以随便调用的。他必须包含在对应的synchronized语句中,无论是wait还是notify都需要首先获得目标对象的一个监视器。

这里给出简单实用waitnotify的案例:


import java.util.Objects;
public class fist{
	final static Object object = new Object();
	
	public static class MyThread_1 extends Thread{
		@Override
		public void run(){
			synchronized (object) {
				System.out.println(System.currentTimeMillis()+"T1 start");
				try {
					System.out.println(System.currentTimeMillis()+"T1 wait");
					object.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(System.currentTimeMillis()+"T1 end");
			}
		}
	}
	public static class MyThread_2 extends Thread{
		@Override
		public void run(){
			synchronized (object) {
				System.out.println(System.currentTimeMillis()+"T2 start and notify");
				object.notify();
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static void main(String args[]){
		Thread t1 = new MyThread_1();
		Thread t2 = new MyThread_2();
		t1.start();
		t2.start();
	}
}

上述代码中,开启了两个线程t1t2t1执行了object.wait()方法。注意,在wait方法执行前,t1申请了object对象锁。因此在执行object.wait()时,他说持有锁的。Wait()方法执行后,t1会进行等待,并且释放object对象的锁。t2在执行notify之前也会获得object对象锁,在notify执行后释放object对象的锁。

输出结果如下:



6.挂起(suspend)和继续执行(resume)

这两个操作是一对相反的操作,被挂起的线程必须等到resume()操作后才能继续执行。但如果仔细阅读文档后会发现,他们早已被标注为废弃方法,不推荐使用。

不推荐使用suspend()去挂起线程的原因,是因为suspend()在导致线程暂停的同时,并不会去释放任何资源锁。此时,任何线程想访问它占用的锁时,都会被牵连,无法正常运行。

如果,resume操作意外的再suspend前就执行了,那么被挂起的线程就很难有机会继续执行了。并且更严重的是他的锁并不会被释放,因此可能会导致整个系统无法正常工作。而且,对于被挂起的线程,从线程状态上看,居然还是runnable,这也会严重的影响到我们对系统当前状态的判断。

import java.util.Objects;
public class fist{
	final static Object object = new Object();
	
	public static class MyThread extends Thread{
		public MyThread(String name){
			super.setName(name);
		}
		@Override
		public void run(){
			synchronized (object) {
				System.out.println("in "+getName());
				Thread.currentThread().suspend();
			}
                        System.out.println("end "+getName());
		}
	}
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread("t1");
		Thread t2 = new MyThread("t2");
		t1.start();
		Thread.sleep(2000);
		t2.start();
		t1.resume();
		t2.resume();
	}
}

输出:



这段代码的程序不会退出。而是会挂起。使用jstack命令打印系统线程信息可以看到。

这时我们需要注意,当前系统中,t2其实是被挂起的。但是他的线程状态是runnable,这很有可能对导致我们误判当前系统的状态。同时,虽然主程序已经调用了resume(),但是由于时间先后顺序的缘故,resume并没有生效!这就导致了线程t2被永久挂起,并且永久占用了对象object的锁。这对于系统来说极有可能是致命的。

没有输出”end t2”的原因是t2.resume()t2.suspend()前就运行了,导致t2永远被挂起,如果把代码改写成如下,才会输出”end t2”。

import java.util.Objects;

public class fist{
	final static Object object = new Object();
	
	public static class MyThread extends Thread{
		public MyThread(String name){
			super.setName(name);
		}
		@Override
		public void run(){
			synchronized (object) {
				System.out.println("in "+getName());
				Thread.currentThread().suspend();
			}
			System.out.println("end "+getName());
		}
	}
	public static void main(String args[]) throws InterruptedException{
		Thread t1 = new MyThread("t1");
		Thread t2 = new MyThread("t2");
		t1.start();
		Thread.sleep(2000);   //设置延迟
		t2.start();
		t1.resume();
		t2.resume();
	}
}

输出:





7.等待线程结束(join)和谦让(yield)

很多时候,一个线程的输入可能会非常依赖另一个或多个线程的输出,此时,这个线程就需要等待依赖线程的执行完毕,才能继续执行。JDK提供了join()操作来实现这个功能,如下所示,显示了两个join()方法:

public final void join() throws InterruptedException

public final synchronized void join() throws InterruptedException

第一个join方法表示无限等待,他会一直阻塞线程,直到目标线程执行完毕。第二个给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会“等不及了”,而继续往下执行。

这里提供一个简单的join实例,以供参考:

import java.util.Objects;

public class fist{
	public volatile static int  i = 0;
	public static class MyThread extends Thread{
		@Override
		public void run(){
			for(i=0;i<10000000;i++);
		}
	}
	
	public static void main(String args[]) throws InterruptedException{
		Thread t = new MyThread();
		t.start();
		t.join();
		System.out.println(i);
	}
}

主函数中,如果不使用join等待MyThread线程,那么得到的i可能是0或者是一个很小的数值。因为MyThread还没开始执行,i的值就被打印出来了。但在使用join方法后,表示主线程愿意等待MytThread线程执行完毕,跟着MyThread一起往前走,故在join返回时,MyThread已经执行完毕,故i总是10000000

另外一个比较有趣的方法,Thread.yield()

这是一个静态方法,一旦执行,它会使当前线程让出cpu。但要注意让出cpu不代表不在执行了。当前线程在让出cpu后,还会进行cpu的资源争夺,但是能否被在次分配到,就不一定了。

如果你觉得一个线程不那重要,或者优先级非常低,而且又害怕它会占用太多的cpu资源,那么可以在适当的时候调用yield,给予其他重要线程更多工作机会。





猜你喜欢

转载自blog.csdn.net/huxiny/article/details/79731292
今日推荐