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线程,每个线程都在操作自己内部的数据,线程之间互不影响

Java——多线程之线程间通信的更多相关文章

  1. java多线程与线程间通信

    转自(http://blog.csdn.net/jerrying0203/article/details/45563947) 本文学习并总结java多线程与线程间通信的原理和方法,内容涉及java线程 ...

  2. Java多线程基础——线程间通信

    在使用多线程的时候,经常需要多个线程进行协作来完成一件事情.在前面两章分析了Java多线程的基本使用以及利用synchronized来实现多个线程同步调用方法或者执行代码块.但上面两章的内容涉及到的例 ...

  3. Java多线程:线程间通信之volatile与sychronized

    由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信. Java为线程间通信提供了三个相关的关键字volatile, synchronized ...

  4. Java多线程:线程间通信之Lock

    Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...

  5. java多线程:线程间通信——生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...

  6. Java多线程之线程的通信

    Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...

  7. 0038 Java学习笔记-多线程-传统线程间通信、Condition、阻塞队列、《疯狂Java讲义 第三版》进程间通信示例代码存在的一个问题

    调用同步锁的wait().notify().notifyAll()进行线程通信 看这个经典的存取款问题,要求两个线程存款,两个线程取款,账户里有余额的时候只能取款,没余额的时候只能存款,存取款金额相同 ...

  8. 06_Java多线程、线程间通信

    1. 线程的概念      1.1多进程与多线程 进程:一个正在执行的程序.每个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个控制单元. 一个进程至少有一个线程. 线程:就是进程中的一个独立 ...

  9. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

随机推荐

  1. hadoop(六)rsync远程同步|xsync集群分发(完全分布式准备三)|8

    前置环境准备:centos7克隆ip|机器名|映射关系|别名配置(hadoop完全分布式准备一)scp命令copy文件和配置(hadoop完全分布式准备二) rsync远程同步工具 优点 rsync主 ...

  2. Linux c++ vim环境搭建系列(3)——Ubuntu18.04.4编译安装youcompleteme

    3. youcompleteme编译安装 参考网址: https://github.com/ycm-core/YouCompleteMe#linux-64-bit 建议不要用这个博客的方法: http ...

  3. 虚拟机VMware 安装后虚拟机网卡与主机网卡数据交换关系

    安装好虚拟机以后,在网络连接里面可以看到多了两块网卡: 其中VMnet1是虚拟机Host-only模式的网络接口,VMnet8是NAT模式的网络接口,这些后面会详细介绍.在VMware Worksta ...

  4. 【Java】抽象类、接口

    什么是抽象类? 特点: - 抽象类几乎普通类一样,除了不能实例化 - 不能实例化不代表没有构造器,依然可以声明构造器,便于子类实例化调用 - 具有抽象方法的类,一定是抽象类 abstract 抽象的 ...

  5. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十二)之Error Handling with Exceptions

    The ideal time to catch an error is at compile time, before you even try to run the program. However ...

  6. Java 8 到 Java 14,改变了哪些你写代码的方式?

    前几天,JDK 14 正式发布了,这次发布的新版本一共包含了16个新的特性. 其实,从Java8 到 Java14 ,真正的改变了程序员写代码的方式的特性并不多,我们这篇文章就来看一下都有哪些. La ...

  7. vue-cli目录结构分析

    vue3初始化一个项目,查看其项目结构,会发现比vue2的更加简洁. package.json说明 项目的配置文件,定义了项目的基本信息以及项目的相关包依赖,npm运行命令等,位于项目根目录. scr ...

  8. v&n赛 ML 第一步(python解决)

    题目链接 给了70组x,y,根据提示,是求拟合曲线,再通过x求y 知道MATLAB应该录入就能解决吧,但是没下这软件,试试用python解决 #coding:utf- from pwn import ...

  9. 《并发编程的艺术》阅读笔记之Volatile

    来源 在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不 ...

  10. sk-learn实现L2岭回归,对线性回归正则化

    岭回归算法: from sklearn.datasets import load_boston from sklearn.externals import joblib from sklearn.li ...