Java多线程系列——线程间通信

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Java多线程系列——线程间通信

用代码征服天下   2019-12-02 我要评论

 

Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线程的深入剖析。

 

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。

 

线程运行状态

 

 

 

1)新创建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢占到CPU资源,此线程就处于Running(运行)状态。  

2)Runnable状态和Running状态可相互切换,因为有可能线程运行一段时间后,有其他高优先级的线程抢占了CPU资源,这时此线程就从Running状态变成Runnable状态。线程进入Runnable状态大体分为如下5种情况:

  1. 调用sleep()方法后经过的时间超过了指定的休眠时间。
  2. 线程调用的阻塞I0已经返回,阻塞方法执行完毕。
  3. 线程成功地获得了试图同步的监视器。
  4. 线程正在等待某个通知,其他线程发出了通知。
  5. 处于挂起状态的线程调用了resume恢复方法。

3)Blocked是阻塞的意思,例如遇到了一个IO操作,此时CPU处于空闲状态,可能会转而把CPU时间片分配给其他线程,这时也可以称为“暂停”状态。Blocked状态结束后,进入Runnable状态,等待系统重新分配资源。出现阻塞的情况有以下几种:

  1. 线程调用sleep()方法,主动放弃占用的处理器资源。
  2. 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
  3. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
  4. 线程等待某个通知。
  5. 程序调用了suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。

4)run()方法运行结束后进入销毁阶段,整个线程执行完毕。

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。

 

 

等待与通知机制

一、不使用等待通知机制实现线程间通信:

我们先不使用等待通知机制来看下如何实现线程间的通信:

import java.util.ArrayList;
import java.util.List;

class MyList{
	private List list=new ArrayList();
	public void add() {
		list.add("小马");
	}
	
	public int size() {
		return list.size();
	}
}

class ThreadA extends Thread{
	private MyList list;
	
	public ThreadA(MyList list) {
		this.list=list;
	}
	
	@Override
	public void run() {
		try {
			for(int i=0;i<10;i++) {
				list.add();
				System.out.println("添加了"+(i+1)+"个元素");
				Thread.sleep(1000);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

class ThreadB extends Thread{
	private MyList list;
	
	public ThreadB(MyList list) {
		this.list=list;
	}
	
	@Override
	public void run() {
		while(true) {
			if(list.size()==5) {
				System.out.println("==5了,线程b要退出了");
			}
		}
			
	}
}



public class Test {
	
	
	public static void main(String[] args) {
		MyList list=new MyList();
		ThreadA a=new ThreadA(list);
		a.setName("A");
		a.start();
		ThreadB b=new ThreadB(list);
		b.setName("B");
		b.start();
		
	}
	
}

  

运行结果:

添加了1个元素
添加了2个元素
添加了3个元素
添加了4个元素
添加了5个元素
==5了,线程b要退出了
添加了6个元素
添加了7个元素
添加了8个元素
添加了9个元素
添加了10个元素

  

上述代码要实现的是当list的size为5时,B线程进行操作,实现了AB两个线程之间的通信,但是有一个弊端,就是线程B不停的通过while语句轮询机制来检测某一个条件,造成了CPU资源的浪费。

如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是“wait/notify”机制。

 

二、wait/notify机制:

1、什么是等待通知机制:

等待/通知机制在生活中比比皆是,比如在就餐时就会实现

 

 

 

1)厨师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间也不确定。

2)服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)的状态。

3)服务员如何能取到菜呢?这又得取决于厨师,厨师将菜放在“菜品传递台”上,其实就相当于一种通知(notify),这时服务员才可以拿到菜并交给就餐者。

4)在这个过程中出现了“等待/通知”机制。

需要说明的是,前面示例中多个线程之间也可以实现通信,原因就是多个线程共同访问同一个变量,但那种通信机制不是“等待/通知”,两个线程完全是主动式地读取一个共享变量,在花费读取时间的基础上,读到的值是不是想要的,并不能完全确定。所以现在迫切需要一种“等待/通知”机制来满足上面的需求。

 

2、等待通知机制的实现:

wait()方法:

方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IlegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch 语句进行捕捉异常。

 

notify()方法:

方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出llegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify 或notifyAll。

 

总结:wait使线程停止运行,而notify使停止的线程继续运行

 

我们现在再来用等待通知机制来实现上面的案例,代码如下:

 

import java.util.ArrayList;
import java.util.List;

class MyList{
	private static List list=new ArrayList();
	public static void add() {
		list.add("小马");
	}
	
	public static int size() {
		return list.size();
	}
}

class ThreadA extends Thread{
	
	private Object lock;
	
	public ThreadA(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		try {
			synchronized(lock) {
				if(MyList.size()!=5) {
					System.out.println("wait begin "+System.currentTimeMillis());
					lock.wait();
					System.out.println("wait end   "+System.currentTimeMillis());
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

class ThreadB extends Thread{
private Object lock;
	
	public ThreadB(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		try {
			synchronized(lock) {
				for(int i=0;i<10;i++) {
					MyList.add();
					if(MyList.size()==5) {
						lock.notify();
						System.out.println("已发出通知");
					}
					System.out.println("添加了"+(i+1)+"个元素");
					Thread.sleep(1000);
				}
			}	
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}



public class Test {
	
	
	public static void main(String[] args) throws InterruptedException {
		Object lock=new Object();
		ThreadA a=new ThreadA(lock);
		a.start();
		Thread.sleep(1000);
		ThreadB b=new ThreadB(lock);
		b.start();
		
	}
	

}

 

  

运行结果:

wait begin 1575266897805
添加了1个元素
添加了2个元素
添加了3个元素
添加了4个元素
已发出通知
添加了5个元素
添加了6个元素
添加了7个元素
添加了8个元素
添加了9个元素
添加了10个元素
wait end   1575266908861

  

从结果中我们可以看出,程序一启动时就输出了wait begin,但是并没有立即输出wait end,这是因为调用了wait()方法,使当前线程处于等待状态,暂停执行,当list的size等于5时,调用了notify()方法,释放了等待的线程,wait end 便得以输出读者可能会有疑惑,为什么已经执行了notify方法,但是wait end并没有立即输出,而是在结尾才输出,这是因为notify必须在执行完同步synchronized代码块后才释放锁。

关键字synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每个Object 都实现了wait)和notify0方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait)方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新换醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。

wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个”线程。
notifyAll()方法可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,因为这取决于JVM虚拟机的实现。

 

3、notifyAll()的使用:

notify()方法每次只可以唤醒一个线程,notifyAll()方法则可以唤醒所有线程

//等待线程
class Service{
	public void testMethod(Object lock) {
		try {
			synchronized(lock) {
				System.out.println("begin wait() ThreadName="+Thread.currentThread().getName());
				lock.wait();
				System.out.println("end wait() ThreadName="+Thread.currentThread().getName());
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

//唤醒线程
class NotifyThread extends Thread{
	private Object lock;
	
	public NotifyThread(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		synchronized(lock) {
			lock.notify();
		}
	}
}

class ThreadA extends Thread{
	private Object lock;
	public ThreadA(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		Service service=new Service();
		service.testMethod(lock);
	}
}

class ThreadB extends Thread{
	private Object lock;
	public ThreadB(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		Service service=new Service();
		service.testMethod(lock);
	}
}

class ThreadC extends Thread{
	private Object lock;
	public ThreadC(Object lock) {
		this.lock=lock;
	}
	
	@Override
	public void run() {
		Service service=new Service();
		service.testMethod(lock);
	}
}




public class Test {
	
	
	public static void main(String[] args) throws InterruptedException {
		Object lock=new Object();
		ThreadA a=new ThreadA(lock);
		a.start();
		ThreadB b=new ThreadB(lock);
		b.start();
		ThreadC c=new ThreadC(lock);
		c.start();
		Thread.sleep(1000);
		NotifyThread notifyThread=new NotifyThread(lock);
		notifyThread.start();
		
	}
	

}

  

运行结果:

begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
end wait() ThreadName=Thread-0

  

在唤醒线程中我们使用了notify()方法,从结果我们可以看出只有一个线程被唤醒了,其他线程依然处于等待状态,这时我们把notify()修改成notifyAll()方法,则运行结果如下:

begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-0
end wait() ThreadName=Thread-1
end wait() ThreadName=Thread-0
end wait() ThreadName=Thread-2

  

可以看到所有等待的线程都已经被释放

 

4、生产者/消费者模式实现

等待/通知模式最经典的案例就是“生产者/消费者”模式。但此模式在使用上有几种“变形”,还有一些小的注意事项,但原理都是基于wait/notify的。

(1)、一个生产者和一个消费者:操作值

共同操作的值:

public class ValueObject {
	
	public static String value="";

}

  

生产者:生产者生产东西

public class Product {
	
	private String lock;
	
	public Product(String lock) {
		this.lock=lock;
	}
	
	public void setValue() {
		try {
			synchronized (lock) {
				if(!ValueObject.value.equals("")) {
					lock.wait();
				}
				String value=System.currentTimeMillis()+"_"+System.nanoTime();
				System.out.println("set的值是"+value);
				ValueObject.value=value;
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

消费者:消费者消费东西

public class Customer {
	
	private String lock;
	public Customer(String lock) {
		this.lock=lock;
	}
	
	public void getValue() {
		try {
			synchronized(lock) {
				if(ValueObject.value.equals("")) {
					lock.wait();
				}
				System.out.println("get的值是"+ValueObject.value);
				ValueObject.value="";
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

测试代码:

class ThreadP extends Thread{
	private Product p;
	
	public ThreadP(Product p) {
		this.p=p;
	}
	
	@Override
	public void run() {
		while(true) {
			p.setValue();
		}
	}
}

class ThreadC extends Thread{
	private Customer c;
	
	public ThreadC(Customer c) {
		this.c=c;
	}
	
	@Override
	public void run() {
		while(true) {
			c.getValue();
		}
	}
}

public class Run {
	
	public static void main(String[] args) {
		String lock=new String("");
		Product p=new Product(lock);
		Customer c=new Customer(lock);
		ThreadP pThreadP=new ThreadP(p);
		ThreadC cThreadC=new ThreadC(c);
		pThreadP.start();
		cThreadC.start();
	}

}

  

部分运行结果:

set的值是1575270909669_589770446584700
get的值是1575270909669_589770446584700
set的值是1575270909669_589770446607900
get的值是1575270909669_589770446607900
set的值是1575270909669_589770446631300
get的值是1575270909669_589770446631300
set的值是1575270909669_589770446654500
get的值是1575270909669_589770446654500
set的值是1575270909669_589770446678200
get的值是1575270909669_589770446678200
set的值是1575270909669_589770446701400
get的值是1575270909669_589770446701400
set的值是1575270909669_589770446724800

  

此实例生产者生产一个产品,消费者消费一个产品,在代码中就是对ValueObject中的value值进行操作

 

(2)多生产与多消费:操作值

上一个示例只有一个生产者和一个消费者,但现实生活中通常不会只有一个,下面我们来看一下多生产多消费的情况

共同操作的值:

public class ValueObject {
	
	public static String value="";

}

  

生产者:

public class Product {
	
	private String lock;
	
	public Product(String lock) {
		this.lock=lock;
	}
	
	public void setValue() {
		try {
			synchronized (lock) {
				if(!ValueObject.value.equals("")) {
					System.out.println("生产者"+Thread.currentThread().getName()+"WATING了★");
					lock.wait();
				}
				System.out.println("生产者"+Thread.currentThread().getName()+"RUNNABLE了☆");
				String value=System.currentTimeMillis()+"_"+System.nanoTime();
				System.out.println("set的值是"+value);
				ValueObject.value=value;
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

消费者:

public class Customer {
	
	private String lock;
	public Customer(String lock) {
		this.lock=lock;
	}
	
	public void getValue() {
		try {
			synchronized(lock) {
				if(ValueObject.value.equals("")) {
					System.out.println("消费者"+Thread.currentThread().getName()+"WATING了★");
					lock.wait();
				}
				System.out.println("消费者"+Thread.currentThread().getName()+"RUNNABLE了☆");
				System.out.println("get的值是"+ValueObject.value);
				ValueObject.value="";
				lock.notify();
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

测试类:

import java.beans.ParameterDescriptor;

import javax.swing.text.rtf.RTFEditorKit;

class ThreadP extends Thread{
	private Product p;
	
	public ThreadP(Product p) {
		this.p=p;
	}
	
	@Override
	public void run() {
		while(true) {
			p.setValue();
		}
	}
}

class ThreadC extends Thread{
	private Customer c;
	
	public ThreadC(Customer c) {
		this.c=c;
	}
	
	@Override
	public void run() {
		while(true) {
			c.getValue();
		}
	}
}

public class Run {
	
	public static void main(String[] args) throws InterruptedException {
		String lock=new String("");
		Product p=new Product(lock);
		Customer c=new Customer(lock);
		ThreadP[] pThreadP=new ThreadP[2];
		ThreadC[] cThreadC=new ThreadC[2];
		for(int i=0;i<2;i++) {
			pThreadP[i]=new ThreadP(p);
			pThreadP[i].setName("生产者"+(i+1));
			cThreadC[i]=new ThreadC(c);
			cThreadC[i].setName("消费者"+(i+1));
			pThreadP[i].start();
			cThreadC[i].start();
		}
		Thread.sleep(5000);
		Thread[] threadArray=new Thread[Thread.currentThread().getThreadGroup().activeCount()];
		for(int i=0;i<threadArray.length;i++) {
			System.out.println(threadArray[i].getName()+" "+threadArray[i].getState());
		}
		
	}

}

  

部分运行结果:

生产者生产者2RUNNABLE了☆
生产者生产者2WATING了★
消费者消费者1RUNNABLE了☆
消费者消费者1WATING了★
生产者生产者1RUNNABLE了☆
生产者生产者1WATING了★
生产者生产者2RUNNABLE了☆
生产者生产者2WATING了★
消费者消费者1RUNNABLE了☆
消费者消费者1WATING了★
生产者生产者1RUNNABLE了☆
生产者生产者1WATING了★
生产者生产者2RUNNABLE了☆

  

上面的示例就是多个生产者和多个消费者的情况

但是有时候结果可能并没有我们想象中的那么美好,多生产者消费者可能会出现假死状态,比如下面的情况:

生产者生产者1RUNNABLE了
生产者生产者1WAITING了★
生产者生产者2WAITING了★
消费者消费者2 RUNNABLE了
消费者消费者2 WAITING了☆
消费者消费者1WAITING了☆
生产者生产者1RUNNABLE了
生产者生产者1WAITING了★
生产者生产者2 WAITING了★
main RUNNABLE
生产者1WAITING
消费者1WAITING
生产者2 WAITING
消费者2 WAITING

  

我们来逐步分析一下为什么会出现这样的结果:

1)生产者1进行生产,生产完毕后发出通知(但此通知属于“通知过早”),并释放锁,准备进入下一次的while循环。

2)生产者1进入了下一次while循环,迅速再次持有锁,发现产品并没有被消费,所以生产者1呈等待状态。

3)生产者2被start()启动,生产者2发现产品还没有被消费,所以生产者2也呈等待状态。

4)消费者2被start()启动,消费者2持有锁,将产品消费并发出通知(发出的通知唤醒了第7行生产者1),运行结束后释放锁,等待消费者2进入下次循环。

5)消费者2进入了下一次的while循环,并持有锁,发现产品并未生产,所以释放锁并呈等待状态。

6)消费者1被start()启动,快速持有锁,发现产品并未生产,所以释放锁并呈等待状态。

7)由于消费者2在第4行已经将产品进行消费,唤醒了第7行的生产者1进行顺利生产后释放锁,并发出通知(此通知唤醒了第9行的生产者2),生产者1准备进入下一次的while循环。

8)这时生产者1进人下一次的while循环再次持有锁,发现产品还并未消费,所以生产者1也呈等待状态。

9)由于第7行的生产者1唤醒了生产者2,生产者2发现产品还并未被消费,所以生产者2也呈等待状态。

出现符号就代表本线程进入等待状态,需要额外注意这样的执行结果。

假死出现的主要原因是有可能连续唤醒同类。怎么能解决这样的问题呢?不光唤醒同类,将异类也一同唤醒就解决了。

 

解决假死问题:

解决假死问题其实很简单,就是将Product类和Customer类中的notify()方法修改为notifyAll()方法即可,它的原理就是不光通知同类线程,也包括异类,这样就不至于出现假死状态了,程序会一直运行下去。

 

方法join的使用:

在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

在介绍join之前我们先来看一段代码:

class MyThread extends Thread{
	@Override
	public void run() {
		try {
			int secondValue=(int)(Math.random()*10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


public class Test{
	
	public static void main(String[] args) {
		MyThread threadTest=new MyThread();
		threadTest.start();
		//Thread.sleep(?);
		System.out.println("我想当threadTest对象执行完毕后在执行");
		System.out.println("但是上面代码中的sleep()中的值应该写多少呢");
		System.out.println("答案是:根据不能确定");
	}

}

  

运行结果:

我想当threadTest对象执行完毕后在执行
但是上面代码中的sleep()中的值应该写多少呢
答案是:根据不能确定
6309

  

上述代码想要通过sleep()方法实现当threadTest线程执行完毕后再执行主线程,但是我们并不知道threadTest线程会执行多长时间,所以这里的sleep不知道该填写多少。

 

1、用join()方法来解决:

class MyThread extends Thread{
	@Override
	public void run() {
		try {
			int secondValue=(int)(Math.random()*10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


public class Test{
	
	public static void main(String[] args) {
		try {
			MyThread threadTest=new MyThread();
			threadTest.start();
			threadTest.join();
			System.out.println("我想当threadTest对象执行完毕后我在执行,我做到了");
			
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

运行结果:

2122
我想当threadTest对象执行完毕后我在执行,我做到了

  

方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。

方法join具有使线程排队运行的作用,有些类似同步的运行效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而sychronized关键字使用的是“对象监视器”原理做为同步。

 

2、方法join(long)的使用:

方法join(long)中的参数是设定等待的时间

class MyThread extends Thread{
	@Override
	public void run() {
		try {
			System.out.println("begin Timer="+System.currentTimeMillis());
			Thread.sleep(5000);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}


public class Test01 {
	
	public static void main(String[] args) {
		try {
			MyThread thread=new MyThread();
			thread.start();
			thread.join(2000);
			System.out.println("end time="+System.currentTimeMillis());
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

  

运行结果:

begin Timer=1575277463900
end time=1575277465900

  

结果等待两秒再执行,之前的博客中我们还学习过一个方法可以让线程暂停执行sleep(long),join和sleep方法都可以让线程暂停,那么两者有什么区别?

 

方法join(long)与sleep(long)的区别:

方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。

join方法的源码如下:

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

  

从源代码中可以了解到,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。

 

类ThreadLocal的使用:

变量值的共享可以使用public static变量的形式,所有的线程都使用同一个public static变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供的类ThreadLocal正是为了解决这样的问题。
类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。

 

1、get()方法与null

public class Test01 {
	
	public static ThreadLocal t1=new ThreadLocal();
	public static void main(String[] args) {
		if(t1.get()==null) {
			System.out.println("从未放过值");
			t1.set("我的值");
		}
		System.out.println(t1.get());
		System.out.println(t1.get());
	}

}

  

运行结果:

从未放过值
我的值
我的值

  

类ThreadLocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程中的值是可以放入ThreadLocal中进行保存的。

 

ThreadLocal线程变量间的隔离性:

class Tools{
	public static ThreadLocal t1=new ThreadLocal();
}

class ThreadE extends Thread{
	@Override
	public void run() {
		try {
			for(int i=0;i<10;i++) {
				Tools.t1.set("ThreadE"+(i+1));
				System.out.println("ThreadE get Value"+Tools.t1.get());
				Thread.sleep(200);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

class ThreadG extends Thread{
	@Override
	public void run() {
		try {
			for(int i=0;i<10;i++) {
				Tools.t1.set("ThreadG"+(i+1));
				System.out.println("ThreadG get Value"+Tools.t1.get());
				Thread.sleep(200);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

public class Test01 {
	
	public static void main(String[] args) {
		try {
			ThreadE e=new ThreadE();
			ThreadG g=new ThreadG();
			e.start();
			g.start();
			for(int i=0;i<10;i++) {
				Tools.t1.set("Main"+(i+1));
				System.out.println("Main get Value="+Tools.t1.get());
				Thread.sleep(200);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	

}

  

运行结果:

Main get Value=Main1
ThreadG get ValueThreadG1
ThreadE get ValueThreadE1
ThreadE get ValueThreadE2
ThreadG get ValueThreadG2
Main get Value=Main2
ThreadG get ValueThreadG3
ThreadE get ValueThreadE3
Main get Value=Main3
ThreadE get ValueThreadE4
ThreadG get ValueThreadG4
Main get Value=Main4
ThreadE get ValueThreadE5
ThreadG get ValueThreadG5
Main get Value=Main5
Main get Value=Main6
ThreadG get ValueThreadG6
ThreadE get ValueThreadE6
ThreadE get ValueThreadE7
ThreadG get ValueThreadG7
Main get Value=Main7
Main get Value=Main8
ThreadG get ValueThreadG8
ThreadE get ValueThreadE8
ThreadE get ValueThreadE9
Main get Value=Main9
ThreadG get ValueThreadG9
Main get Value=Main10
ThreadG get ValueThreadG10
ThreadE get ValueThreadE10

  

上面代码中共有三个线程,ThreadE,ThreadG和main线程,每个线程都在操作自己内部的数据,线程之间互不影响

 

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们