多线程学习(3)线程之间的通讯方式

思考题:html

JAVA多线程之当一个线程在执行死循环时会影响另一个线程吗?java

会,由于当一个没有实际做用的线程作密集型轮询的时候,它消耗了CPU性能,占用了CPU时间,使得其余线程须要等待更长的时间获取CPU时间片断。多线程

线程间的通讯方式

synchronized

synchronized用于多线程设计,有了synchronized关键字,多线程程序的运行结果将变得能够控制。synchronized关键字用于保护共享数据。并发

synchronized实现同步的机制:synchronized依靠"锁"机制进行多线程同步,"锁"有2种,一种是对象锁,一种是类锁异步

  • 1.依靠对象锁 锁定

初始化一个对象时,会自动有一个对象锁。分布式

synchronized (对象){方法块}依靠对象锁工做,多线程访问synchronized修饰的方法方法或方法块时,一旦某个进程抢得锁以后,其余的进程只有排队对待。ide

synchronized void method{} 功能上,等效于

void method{

   synchronized(this) {

    ...

   }

}

下面是举例:函数

public class Thread5 extends Thread {
	private Test test;

	public Thread5(Test test) {
		this.test = test;
	}

	public void run() {
		test.method3();
	}
}


public class Thread6 extends Thread {
	private Test test;

	public Thread6(Test test) {
		this.test = test;
	}

	public void run() {
		test.method4();
	}
}


public class Test {

	public synchronized void method3() {
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep(1000);
				System.out.println("print method3");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}

	public synchronized void method4() {
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep(1000);
				System.out.println("print method4");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}

    public static void main(String[] args) {
        Test test1 = new Test();
		Thread5 thread5 = new Thread5(test1);
		Thread6 thread6 = new Thread6(test1);
		thread5.start();
		thread6.start();
	}
}




输出结果:
print method3
print method3
print method3
print method3
print method3
print method4
print method4
print method4
print method4
print method4

看上面的例子,咱们在建立线程时,传入了同一个Test对象,去执行不一样的synchronized修饰的方法,运行时多线程会去争抢同一个对象锁,结果变成同步执行。若是咱们在建立线程时,传入不一样的对象,那它们获取到的对象锁就不是同一个,结果就变成并发执行了。高并发

public static void main(String[] args) {
		Test test1 = new Test();
		Test test2 = new Test();
		Thread5 thread5 = new Thread5(test1);
		Thread6 thread6 = new Thread6(test2);
		thread5.start();
		thread6.start();
	}

输出结果:
print method3
print method4
print method4
print method3
print method3
print method4
print method4
print method3
print method3
print method4

synchronized void method{}整个函数加上synchronized块,效率并很差。在函数内部,可能咱们须要同步的只是小部分共享数据,其余数据,能够自由访问,这时候咱们能够用 synchronized(表达式){//语句}更加精确的控制。性能

这里讲的同步是指多个线程经过synchronized关键字这种方式来实现线程间的通讯。

  • 2.使用类对象锁去作线程的共享互斥.
synchronized static void method(){} 此代码块等效于

void method{
   
        synchronized(Obl.class)
  
    }
}

 举例以下:

public class Thread7 {

	public static Thread7 test1 = new Thread7();
	public static Thread7 test2 = new Thread7();

	public synchronized static void method1() throws InterruptedException {
		System.out.println("method1 begin at:" + System.currentTimeMillis());
		Thread.sleep(6000);
		System.out.println("method1 end at:" + System.currentTimeMillis());
	}

	public synchronized static void method2() throws InterruptedException {
		while (true) {
			System.out.println("method2 running");
			Thread.sleep(200);
		}
	}

	public static void main(String[] args) {
		Thread thread1 = new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					// Thread7.method1();
					test1.method1();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				for (int i = 1; i < 4; i++) {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("thread1 still alive");
				}

			}

		});

		Thread thread2 = new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					// Thread7.method2();
					test2.method2();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}

		});

		thread1.start();
		thread2.start();

	}
}


输出结果:
method1 begin at:1554173225933
method1 end at:1554173231934
method2 running
thread1 still alive
method2 running
thread1 still alive
method2 running
thread1 still alive
method2 running
method2 running
method2 running
method2 running
method2 running
method2 running
method2 running
method2 running
method2 running
method2 running
method2 running

从上面的例子,能够看出,当类的方法是静态方法,且由synchronized修饰后。在多个线程的run方法中,不论是经过类名.方法名执行(不一样的线程能够执行不一样的方法,这些方法都是由synchronized static修饰的),仍是经过对象.方法名执行(不一样的线程能够持有不一样的对象,但它们都是一个类型的),高并发状况下,它们也会由于类对象锁,而按前后顺序,同步执行,一个线程持有锁,其余线程要等待。

下面咱们延伸一个例子:

public class Thread7 {

	public static Thread7 test1 = new Thread7();
	public static Thread7 test2 = new Thread7();

	public synchronized static void method1() throws InterruptedException {
		System.out.println("method1 begin at:" + System.currentTimeMillis());
		Thread.sleep(3000);
		System.out.println("method1 end at:" + System.currentTimeMillis());
	}

	public synchronized static void method2() throws InterruptedException {
		while (true) {
			System.out.println("method2 running");
			Thread.sleep(200);
		}
	}
	
	public synchronized void method3() throws InterruptedException {
		while (true) {
			System.out.println("method3 running");
			Thread.sleep(200);
		}
	}

	public static void main(String[] args) {
		Thread thread1 = new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					 Thread7.method1();
					 // test1.method1();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				for (int i = 1; i < 4; i++) {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("thread1 still alive");
				}

			}

		});

		Thread thread2 = new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					// Thread7.method2();
					test2.method2();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}

		});
		
		Thread thread3 = new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					// Thread7.method2();
					test2.method3();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			}

		});

		thread1.start();
		thread2.start();
		thread3.start();

	}
}

输出结果:
method1 begin at:1554174080333
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method3 running
method1 end at:1554174083333
method2 running
method3 running
method2 running
thread1 still alive
method3 running
thread1 still alive
method2 running
method3 running
thread1 still alive
method2 running
method3 running
method2 running
method3 running
method2 running
method3 running
method2 running
method3 running
method2 running
method3 running
method2 running
method3 running
method2 running
method3 running

上面这个例子,在类中synchronized既修饰静态方法,又修饰普通方法,咱们建立了4个线程来执行并发,经过输出结果能够看出,类对象锁和对象锁互不影响。当两个线程要执行synchronized修饰的静态方法时,它们争抢类对象锁;当两个线程要执行synchronized修饰的普通方法时,它们争抢对象锁。

3.synchronized 修饰线程的run方法

public class TestSychronized {
    static TestSychronized instance = new TestSychronized();
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
                public synchronized void run() {
                    
                    for(int i=1; i<4; i++) {
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("Thread1 still alive, " + i);
                    }                    
                }
        });
        new Thread(thread1).start();
        new Thread(thread1).start();
    }
}

输出结果:
Thread1 still alive, 1
Thread1 still alive, 2
Thread1 still alive, 3
Thread1 still alive, 1
Thread1 still alive, 2
Thread1 still alive, 3


public class Thread8 extends Thread {
	@Override
	public synchronized void run() {
		for (int i = 1; i < 4; i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Thread still alive, " + i);
		}
	}

	public static void main(String[] args) {
		Thread thread1 = new Thread8();
		Thread thread2 = new Thread8();

		thread1.start();
		thread2.start();
	}
}
输出结果:
Thread still alive, 1
Thread still alive, 1
Thread still alive, 2
Thread still alive, 2
Thread still alive, 3
Thread still alive, 3

为何经过传入Runnable对象的线程执行run方法时是同步的,而extends继承的线程,执行方法时是异步的?

首先看run方法,是一个非静态方法,就是一个普通方法,那么synchronized修饰普通方法,线程争抢的是对象锁。

而咱们获取经过extends继承方式的线程对象时,是new出来的,并非同一个对象。因此执行

thread1.start();

thread2.start();

时不存在对同一个对象锁的争夺,就能够并发执行。

下面咱们来看Thread的run方法源码:

public class Thread implements Runnable {

private Runnable target;	

public void run() {
		if (this.target != null)
			this.target.run();
	}
}


public abstract interface Runnable {
	public abstract void run();
}

从源码咱们能够知道,线程在执行run方法时,经过extends方式继承线程,会重写run方法执行咱们本身的方法。若是经过传入Runnable对象的方式建立线程,线程会执行传入的Runnable对象的run方法!而上面咱们给两个线程传入的是同一个Runnable对象,就说明多个线程,调用的是同一个对象的run方法,而这个方法被synchronized修饰了,它们就须要争抢对象锁,因此就变成同步执行了。

②while轮询的方式

import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 public class MyList {
 5 
 6     private List<String> list = new ArrayList<String>();
 7     public void add() {
 8         list.add("elements");
 9     }
10     public int size() {
11         return list.size();
12     }
13 }
14 
15 import mylist.MyList;
16 
17 public class ThreadA extends Thread {
18 
19     private MyList list;
20 
21     public ThreadA(MyList list) {
22         super();
23         this.list = list;
24     }
25 
26     @Override
27     public void run() {
28         try {
29             for (int i = 0; i < 10; i++) {
30                 list.add();
31                 System.out.println("添加了" + (i + 1) + "个元素");
32                 Thread.sleep(1000);
33             }
34         } catch (InterruptedException e) {
35             e.printStackTrace();
36         }
37     }
38 }
39 
40 import mylist.MyList;
41 
42 public class ThreadB extends Thread {
43 
44     private MyList list;
45 
46     public ThreadB(MyList list) {
47         super();
48         this.list = list;
49     }
50 
51     @Override
52     public void run() {
53         try {
54             while (true) {
55                 if (list.size() == 5) {
56                     System.out.println("==5, 线程b准备退出了");
57                     throw new InterruptedException();
58                 }
59             }
60         } catch (InterruptedException e) {
61             e.printStackTrace();
62         }
63     }
64 }
65 
66 import mylist.MyList;
67 import extthread.ThreadA;
68 import extthread.ThreadB;
69 
70 public class Test {
71 
72     public static void main(String[] args) {
73         MyList service = new MyList();
74 
75         ThreadA a = new ThreadA(service);
76         a.setName("A");
77         a.start();
78 
79         ThreadB b = new ThreadB(service);
80         b.setName("B");
81         b.start();
82     }
83 }

在这种方式下,线程A不断地改变条件,线程ThreadB不停地经过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通讯。可是这种方式会浪费CPU资源。之因此说它浪费资源,是由于JVM调度器将CPU交给线程B执行时,它没作啥“有用”的工做,只是在不断地测试 某个条件是否成立。就相似于现实生活中,某我的一直看着手机屏幕是否有电话来了,而不是: 在干别的事情,当有电话来时,响铃通知TA电话来了。

这种方式还存在另一个问题:

轮询的条件的可见性问题,关于内存可见性问题,可参考:http://www.cnblogs.com/hapjin/p/5492880.html 中的第一点“一,volatile关键字的可见性

线程都是先把变量读取到本地线程栈空间,而后再去再去修改的本地变量。所以,若是线程B每次都在取本地的 条件变量,那么尽管另一个线程已经改变了轮询的条件,它也察觉不到,这样也会形成死循环。

③wait/notify机制

import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 public class MyList {
 5 
 6     private static List<String> list = new ArrayList<String>();
 7 
 8     public static void add() {
 9         list.add("anyString");
10     }
11 
12     public static int size() {
13         return list.size();
14     }
15 }
16 
17 
18 public class ThreadA extends Thread {
19 
20     private Object lock;
21 
22     public ThreadA(Object lock) {
23         super();
24         this.lock = lock;
25     }
26 
27     @Override
28     public void run() {
29         try {
30             synchronized (lock) {
31                 if (MyList.size() != 5) {
32                     System.out.println("wait begin "
33                             + System.currentTimeMillis());
34                     lock.wait();
35                     System.out.println("wait end  "
36                             + System.currentTimeMillis());
37                 }
38             }
39         } catch (InterruptedException e) {
40             e.printStackTrace();
41         }
42     }
43 }
44 
45 
46 public class ThreadB extends Thread {
47     private Object lock;
48 
49     public ThreadB(Object lock) {
50         super();
51         this.lock = lock;
52     }
53 
54     @Override
55     public void run() {
56         try {
57             synchronized (lock) {
58                 for (int i = 0; i < 10; i++) {
59                     MyList.add();
60                     if (MyList.size() == 5) {
61                         lock.notify();
62                         System.out.println("已经发出了通知");
63                     }
64                     System.out.println("添加了" + (i + 1) + "个元素!");
65                     Thread.sleep(1000);
66                 }
67             }
68         } catch (InterruptedException e) {
69             e.printStackTrace();
70         }
71     }
72 }
73 
74 public class Run {
75 
76     public static void main(String[] args) {
77 
78         try {
79             Object lock = new Object();
80 
81             ThreadA a = new ThreadA(lock);
82             a.start();
83 
84             Thread.sleep(50);
85 
86             ThreadB b = new ThreadB(lock);
87             b.start();
88         } catch (InterruptedException e) {
89             e.printStackTrace();
90         }
91     }
92 }

线程A要等待某个条件知足时(list.size()==5),才执行操做。线程B则向list中添加元素,改变list 的size。

A,B之间如何通讯的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?

这里用到了Object类的 wait() 和 notify() 方法。

当条件未知足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU

当条件知足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。

这种方式的一个好处就是CPU的利用率提升了。

可是也有一些缺点:好比,线程B先执行,一会儿添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。由于,线程B已经发了通知了,之后再也不发通知了。这说明:通知过早,会打乱程序的执行逻辑。

④管道通讯就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通讯

具体就不介绍了。分布式系统中说的两种通讯机制:共享内存机制和消息通讯机制。感受前面的①中的synchronized关键字和②中的while轮询 “属于” 共享内存机制,因为是轮询的条件使用了volatile关键字修饰时,这就表示它们经过判断这个“共享的条件变量“是否改变了,来实现进程间的交流。

而管道通讯,更像消息传递机制,也就是说:经过管道,将一个线程中的消息发送给另外一个。

相关文章
相关标签/搜索