线程同步就是线程的同步运行,多个线程必须步调一致, 比如缓冲区, 一个线程向缓冲区写入输入, 要求另一个线程必须同步从缓冲区读出数据
如下代码就无法达到这个要去, 因为现在只是对缓冲区锁定了,没有同步
import java.io.*;
public class Buffer { //缓冲区
private int value;
void put(int i){ //向缓冲区写入数据
value = i;
}
int get(){
return value;
}
public static void main(String args[]){
Buffer buf = new Buffer();
(new Put1(buf)).start();
(new Get1(buf)).start();
}
}
class Put1 extends Thread //向缓冲区写入数据
{
private Buffer bf;
public Put1(Buffer bf){
this.bf = bf;
}
public void run(){
for(int i = 1; i < 6; i++){
synchronized(bf){
bf.put(i);
System.out.println("Put1 put : " + i);
try{
sleep(1);
}catch(InterruptedException e){
System.out.println(e);
}
}
}
}
}
class Get1 extends Thread //从缓冲区读出数据
{
private Buffer bf;
public Get1(Buffer bf){
this.bf = bf;
}
public void run(){
for(int i = 1; i < 6; i++){
synchronized (bf){
System.out.println("\t\t Get1 get :" + bf.get());
try{
sleep(1);
}catch(InterruptedException e){
System.out.println(e);
}
}
}
}
}
多次运行结果不尽相同:
Put1 put : 1
Get1 get :1
Put1 put : 2
Put1 put : 3
Put1 put : 4
Put1 put : 5
Get1 get :5
Get1 get :5
Get1 get :5
Get1 get :5
看到的结果是写入和读出不是同步的, 现在改成如下代码:
import java.io.*;
public class Buffer { //缓冲区
private int value;
private boolean isEmpty = true; //开始缓冲区是空
synchronized void put(int i){
while(!isEmpty){ //如果缓冲区里面有值就一直等待
try{
this.wait(); //等待,并释放互斥锁, 让其他线程执行, 千万要注意这里就已经释放了互斥锁, 也就是在while循环里面没有了锁
}
catch(InterruptedException e){
System.out.println(e);
}
}
//由于前面的wait 就已经释放掉了锁, 所以这里猛的一看是没有锁了, 但是既然跳出了循环执行到这里就说明再一次获得了锁
value = i; //到了这一步就说明上面跳出了循环(isEmpty是true了才会跳出), isEmpty 只所以由
//false 变成 true, 是由于wait的作用, wait释放掉了锁, 别的线程(get方法就有改变isEmpty的能力)才有机会将isEmpty变成true
isEmpty = false; //这个地方必须标记为false, 否则另一个线程get(虽然get是方法)就只能永远在while(isEmpty)里面而无法跳出
notify(); //唤醒其他线程,通知他们可以执行了(最终只会有一个执行),
}
synchronized int get(){
while(isEmpty){
try{
this.wait();
}
catch(InterruptedException e){
System.out.println(e);
}
}
isEmpty = true; //这里的分析跟上面一样
notify();
return value;
}
public static void main(String args[]){
Buffer buf = new Buffer();
(new Put1(buf)).start();
(new Get1(buf)).start();
}
}
class Put1 extends Thread //向缓冲区写入数据
{
private Buffer bf;
public Put1(Buffer bf){
this.bf = bf;
}
public void run(){
for(int i = 1; i < 10; i++){
synchronized(bf){
bf.put(i);
System.out.println("Put1 put : " + i);
try{
sleep(1);
}catch(InterruptedException e){
System.out.println(e);
}
}
}
}
}
class Get1 extends Thread //从缓冲区读出数据
{
private Buffer bf;
public Get1(Buffer bf){
this.bf = bf;
}
public void run(){
for(int i = 1; i < 10; i++){
synchronized (bf){
System.out.println("\t Get1 get :" + bf.get());
try{
sleep(1);
}catch(InterruptedException e){
System.out.println(e);
}
}
}
}
}
运行结果就变成:
Put1 put : 1
Get1 get :1
Put1 put : 2
Get1 get :2
Put1 put : 3
Get1 get :3
Put1 put : 4
Get1 get :4
Put1 put : 5
Get1 get :5
Put1 put : 6
Get1 get :6
Put1 put : 7
Get1 get :7
Put1 put : 8
Get1 get :8
Put1 put : 9
Get1 get :9
现在就达到了一个目的: 一旦向缓冲区写入一个数据, 就马上有线程将那个数据读走, 然后才能再次向缓冲区写入
方法总结:
1.wait()方法是使当前线程阻塞, 并释放互斥锁
2. sleep()方法也是使当前线程阻塞, 但是不释放掉互斥锁
3.调用wait()和 notify()方法之前必须先得到锁, 也就是为什么只能写在synchronized {...} 的代码段里面
4.调用wait()方法,线程A就已经释放了锁, 否则别的线程B就无法得到锁了, 也就是为什么无法在synchronized {..}里面再次唤醒A
5.nodify()方法只能唤醒等待中的一个线程
6.nodifyAll()方法可以唤醒等待的全部, 但是由于等待中的线程想要执行就必须获得锁, 所以最终也只能只有一个线程执行
7.当B调用notifyAll()方法的时候, 如果B仍然持有锁, 那么别的线程就只能等待了
死锁: 多线程同时被阻塞, 他们中的一个或者全部都在等待某个资源被释放或者是都出于等待状态而无法被唤醒时, 由于线程被无限期地阻塞导致了别的线程永远无法执行