Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

时间:2022-12-25 10:42:41

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

1. 新建线程 

  Thread 有一个run()方法,但没有任何需要执行。 

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

注意:不要用run()来开启新线程。它只会在当前线程中,串行执行run()中的代码

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

调用 start方法和直接调用run方法的区别:

start:开启一个新的Thread run:仅仅在当前线程执行

考虑到Java是单继承的,也就是说继承本身也是一种很宝贵的资源,因此,我们也可以使用 Runnable接口来实现同样的操作。

Runnable接口是一个单方法接口,它只有一个run()方法

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作 

public static class ChangeT implements Runnable {

		public ChangeT(long to) {
			this.to = to;
		}

		public void run() {

			while (true) {
				t = to;
				Thread.yield();
			}
		}

		private long to;

	}
new Thread(new ChangeT(111l)).start();

2. 终止线程

Thread.stop() 不推荐使用。它会释放所有锁的monitor 

Thread. stop方法在结束线程时,会直接终止线程,并且会立即释放这个线程所持有的锁而这些锁恰恰是用来维持对象一致性的。

如果此时,写线程写入数据正写到一半,并强行终止那么对象就会被写坏,同时,由于锁已经被释放,

另外一个等待该锁的读线程就顺理成章的读到了这个不一致的对象,悲剧也就此发生

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作 

stop方法强行终止线程导致数据不一致 

首先,对象U持有ID和NAME两个字段,简单起见,这里假设当ID等于NAME时表示对象是一致的,否则表示对象出错。

写线程总是会将ID和NAME写成相同的值,并且在这里在初始值都为0。

当写线程在写对象时,读线程由于无法获得锁,因此必须等待,所以读线程是看不见一个写了一半的对象的。

当写线程写完ID后,很不幸地被 stop,此时对象U的ID为1而NAME仍然为0,处于不一致状态。

而被终止的写线程简单地将锁释放,读线程争夺到锁后读取数据,于是,读到了D=1而NAME=0的错误值

package com.john.learn.high.concurent.ch02.thread.stop;


public class StopThreadUnsafe {

	public static class User {

		public int id = 0;

		public String name = "0";

		@Override
		public String toString() {

			return "Id:" + id + " Name:" + name;
		}
	}

	public static User user = new User();

	public static class ChangeUserThread extends Thread {

		private volatile boolean stop;

		@Override
		public void run() {

			while (!stop) {

				// Lock and block user 
				synchronized (user) {

					user.id = (int) System.currentTimeMillis();

					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {

						e.printStackTrace();
					}

					user.name = user.id + "";

				}

				// 让其他的Thread运行
				Thread.yield();

			}
		}

		public void stopMeOnSafe() {
			this.stop = true;
		}
	}

	public static class ReadUserThread extends Thread {

		@Override
		public void run() {

			while (true) {

				// Lock and block user 
				synchronized (user) {

					if (user.id != Integer.parseInt(user.name)) {

						System.out.println(user);
					}

				}
				// 让其他的Thread运行
				Thread.yield();
			}

		}

	}

	public static void main(String[] args) throws InterruptedException {

		new ReadUserThread().start();
		new ReadUserThread().start();

		while (true) {

			ChangeUserThread t = new ChangeUserThread();
			t.setPriority(10);
			t.start();

			Thread.sleep(200);
			t.stop();

			// t.stopMeOnSafe();
		}

	}

}

运行结果:

输出条件--User 不一致性

if (user.id != Integer.parseInt(user.name)) {

    System.out.println(user);

}

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作


如何实现安全Stop?

package com.john.learn.high.concurent.ch02.thread.stop;


public class StopThreadSafe {


	public static class User {


		public int id = 0;


		public String name = "0";


		@Override
		public String toString() {


			return "Id:" + id + " Name:" + name;
		}
	}


	public static User user = new User();


	public static class ChangeUserThread extends Thread {


		private volatile boolean stop;


		@Override
		public void run() {


			while (!stop) {


				// Lock and block user
				synchronized (user) {


					int oldUserId = user.id;


					user.id = (int) System.currentTimeMillis();


					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {

						e.printStackTrace();

						// rollback
						user.id = oldUserId;
						Thread.currentThread().interrupt();
						this.stop = true;

						continue;
					}

					user.name = user.id + "";


				}


				// 让其他的Thread运行
				Thread.yield();


			}
		}


		public void stopMeOnSafe() {
			this.stop = true;
			interrupt();
		}
	}


	public static class ReadUserThread extends Thread {


		@Override
		public void run() {


			while (true) {


				// Lock and block user
				synchronized (user) {


					if (user.id != Integer.parseInt(user.name)) {


						System.out.println(user);
						continue;
					}


					System.out.println("correct:" + user);


				}
				// 让其他的Thread运行
				Thread.yield();
			}


		}


	}


	public static void main(String[] args) throws InterruptedException {

		new ReadUserThread().start();
		new ReadUserThread().start();

		while (true) {

			ChangeUserThread t = new ChangeUserThread();
			t.start();
			Thread.sleep(200);
			// t.stop();
			t.stopMeOnSafe();


		}


	}


}


3. 线程中断

线程中断是一种重要的线程协作机制。在上一节中,我们已经详细讨论了Stop方法停止线程害处。

严格地讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程有人希望你退出啦!

至于目标线程接到通知后如何处理,则完全由目标线程自行决定,避免线程立即无条件退出,我们就又会遇到stop方法的老问题

与线程中断有关的,有三个方法可能会引起混淆和误用

中断线程 

public void Thread.interrupted() 

判断是否被中断 

public boolean Thread.isinterrupted() 

用来判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态 (Thread.currentThread)

public static boolean Thread.interrupted(); 

--------------------------------------------------------------------------------------------------------------

package com.john.learn.high.concurent.ch02.thread.interrupt;

public class BadInterruptThread {

	public static void main(String[] args) throws InterruptedException {
		
		
		Thread t1 = new Thread() {
			
			public void run() {
				while(true) {
					Thread.yield();
				}
			};
		};
		
		t1.start();
		
		Thread.sleep(1000);
		
		t1.interrupt();
		
		
	}
}

尽管T1进行了中断,但是在T1中并没有中断处理的逻辑

即使T1线程被置上了中断状态,但是这个中断不会发生任何作用。 ---引起大家特别关注

如何实现Good Interrupted?

package com.john.learn.high.concurent.ch02.thread.interrupt;

public class GoodInterruptThread {

	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread() {
			public void run() {
				while (true) {
					
					//查询当前Thread是否中断状态
					if (isInterrupted()) {

						System.out.println("Interrupted.");

						break;
					}

					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {

						System.out.println("Interrupted error.");
						//设置当前线程 中断状态
						Thread.currentThread().interrupt();
					}
					Thread.yield();
				}
			};
		};

		t.start();

		Thread.sleep(1000);

		t.interrupt();

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


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

为了支持多线程之间的协作,JDK提供了两个非常重要的接口线程等待wait()方法和通知notify()方法。

这两个方法并不是在Thread类中的,而是输出Object类,这也意味着任何对象都可以调用这两个方法。

这两个方法的签名如下:

   public final void Object.wait()  throws InterruptedException

   public final void Obejct.notify() 

当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。这是什么意思呢?

比如,线程A中,调用了obj.wait()方法,那么线程A就会停止执行,而转为等待状态等待到何时结束呢?

线程A会一直等到其他线程调用了obj.notify()方法为止。这时,obj对象就俨然成为多个线程之间的有效通信手段。

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

如果一个线程调用了object. wait(),那么它就会进入object对象的等待队列。
这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。
当 object notify()被调用时,它就会从这个等待队列中,随机选择一个线程,并将其唤醒。

这里希望大家注意的是,这个选择是不公平的并不是先等待的线程会优先被选择,这个选择完全是随机的。

除了 notify方法外, Object对象还有一个类似的notifyAll()方法,它和notify的功能基本致,但不同的是,它会唤醒在这个等待队列中所有等待的线程,而不是随机选择一个

package com.john.learn.high.concurent.ch02.thread.waitnotify;


public class SimpleWN {

	private static final Object lock = new Object();

	public static class SimpleWait extends Thread {


		public SimpleWait(String taskName) {
			super(taskName);
		}


		@Override
		public void run() {


			synchronized (lock) {


				System.out.println(System.currentTimeMillis() 
						+ ":" + Thread.currentThread().getName() + " start!");


				try {


					System.out.println(System.currentTimeMillis() 
							+ ":" + getName() + " wait for object!");


					lock.wait();


					System.out.println(System.currentTimeMillis() 
							+ ":" + getName() + " receive notify and not wait!");


				} catch (InterruptedException e) {


					e.printStackTrace();
				}


				System.out.println(System.currentTimeMillis() + ":" + getName() + " end!");
			}
		}


	}


	public static class SimpleNotify extends Thread {


		@Override
		public void run() {


			synchronized (lock) {

				System.out.println(System.currentTimeMillis()
						+ ":T2 start! notify one object");

				lock.notifyAll();

				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {


					e.printStackTrace();
				}


				System.out.println(System.currentTimeMillis()
						+ ":T2 end notify");


			}


		}
	}


	public static void main(String[] args) throws InterruptedException {


		Thread waitThread1 = new SimpleWait("TW1.1");
		Thread waitThread2 = new SimpleWait("TW1.2");


		Thread notifyThread = new SimpleNotify();


		waitThread1.start();
		waitThread2.start();


		Thread.sleep(100);
		notifyThread.start();


	}
}

这里还需要强调一点, Object. wait()方法并不是可以随便调用的。

它必须包含在对应的synchronized语句中,无论是wait()或者notify()都需要首先获得目标对象的一个监视器.

上面SimpleWN实例

Thread waitThread1 = new SimpleWait("TW1.1");
Thread waitThread2 = new SimpleWait("TW1.2");

waitThread1.start();
waitThread2.start();
Thread notifyThread = new SimpleNotify(); lock.notifyAll();

SimpleWN.lock 上wait队列中有两个线程 waitThread1 、 waitThread2 

SimpleNotify Thread 调用 notifyAll , 唤醒所有线程(在SimpleWN.lock wait队列中panding的线程)

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

TW1.1 TW1.2 wait 状态   T2 notify 后, TW1.1 和 TW1.2 启动。

打印的时间戳信息,可以看到,在T2通知T1继续执行后,T1并不能立即影行,而是要等待T2释放Object的锁,并重新成功获得锁后,才能继续执行。因此,加粗部间戳的间隔为2秒(因为T2休眠了2秒)

但是注意: 调用notifyAll 后, 线程Sleep 2s, TW1.1 和 TW1.2 并没有立即唤醒, 等待2s后才启动, 由于wait 必须等待持有lock锁, 但 notifyall 方法没有release lock。

lock.notifyAll();

try {
	Thread.sleep(2000);
} catch (InterruptedException e) {
        e.printStackTrace();
}

注意: Object. wait()和 Thread. sleep()方法都可以让线程等待若干时间。
除了wait被唤醒外,另外一个主要区别就是 wait()方法会释放目标对象的锁Thread. sleep()方法不会释放任何资源。


5. 挂起(Suspend)和继续执行(Resume)线程

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

suspend()不会释放锁 , 如果加锁发生在resume()之前 ,则死锁发生。

package com.john.learn.high.concurent.ch02.thread.suspend;

public class BadSuspend {

	public static Object lock = new Object();

	private static ChangeObjectThread t1 = new ChangeObjectThread("t1");
	private static ChangeObjectThread t2 = new ChangeObjectThread("t2");

	public static class ChangeObjectThread extends Thread {

		public ChangeObjectThread(String threadName) {

			super(threadName);
		}

		@Override
		public void run() {

			synchronized (lock) {

				System.out.println("in thread [" + this.getName() + "] start");

				Thread.currentThread().suspend();
				
				System.out.println("in thread [" + this.getName() + "] end");
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {

		t1.start();
		Thread.sleep(100); // t1已经 进入 run 方法,同时 suspend() 
		t2.start(); //t2 启动 但由于t1 lock 导致 t2 在synchronized (lock)  等待 锁
		t1.resume();
		t2.resume(); // t2 resume() 在 t2 suspend 之前 导致deadlock
		t1.join();
		t2.join();

	}

}

Java高并发程序-Chapter2 Java并行程序基础 (第五讲)线程的基本操作

T1已经 进入 run 方法,获取到lock锁,同时 suspend() 导致 lock锁没有释放,

T2启动由于无法获取到lock锁,T2 在suspend 之前 调用 resume()。 

T1 resume后, 释放lock,但 T2 永久suspend 无法唤醒。

如果需要一个比较可靠的 suspend()函数,那应该怎么办呢?
回想一下上一节中提到的wait()和 notify()方法,这也不是一件难事。

下面的代码就给出了一个利用 wait()和 notify()方法,在用层面实现 suspend()和resume()功能

package com.john.learn.high.concurent.ch02.thread.suspend;

public class GoodSuspend {

	public static Object lock = new Object();

	public static class ChangeObjectThread extends Thread {

		private volatile boolean suspendMe = false;

		@Override
		public void run() {

			while (true) {

				synchronized (this) {

					while (suspendMe) {

						try {
							System.out.println("in ChangeObjectThread to wait!");

							wait();

							System.out.println("in ChangeObjectThread to wait!");

						} catch (InterruptedException e) {

							e.printStackTrace();
						}
					}

				}

				synchronized (lock) {

					System.out.println("in ChangeObjectThread!");
				}

				Thread.yield();

			}

		}

		public void suspendMe() {
			this.suspendMe = true;
		}

		public void resumeMe() {

			this.suspendMe = false;

			synchronized (this) {
				this.notify();
			}

		}

	}

	public static class ReadObjectThread extends Thread {

		@Override
		public void run() {

			while (true) {

				synchronized (lock) {

					System.out.println("in ReadObjectThread.");
				}

				Thread.yield();
			}

		}
	}

	public static void main(String[] args) throws InterruptedException {
		ChangeObjectThread t1 = new ChangeObjectThread();
		ReadObjectThread t2 = new ReadObjectThread();

		t1.start();
		t1.suspendMe();
		Thread.sleep(1000);
		t2.start();
		System.out.println("Suspend t1 2 sec.");
		Thread.sleep(2000);
		System.out.println("Resume t1.");
		t1.resumeMe();

	}
}

suspendMe: 仅仅当前线程 wait状态

resumeMe: notify 当前线程,唤醒wait方法。

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

public final void join() throws InterruptedException

public final synchronized void join(long millis) throws InterruptedException

join的本质 
while (isAlive()){ 
    wait(0); 

线程执行完毕后,操作系统调用notifyAll() 

注意: 不要在Thread实例上使用 thread.wait()和thread.notify()方法 ,因为这很有可能会影响系统API的工作,或者被系统API所影响

public static native void yield();

Thread. yield的调用就好像是在说:我已经完成一些最重要的工作了,我应该是可以休息一下了,可以给其他线程一些工作机会啦!如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的CPU资源,那么可以在适当的时候调用 Thread.yield(),给予其他重要线程更多的工作机会