前言:关于synchronized、wait、notify已经notifyAll大家应该不陌生,现在我大致说一下我的理解。
一:synchronized
synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争某个资源的时候会有先后顺序。在java中有三种写synchronized的方式
- 第一种:
- 写在普通方法的前面,这种表示对实例对象加锁。
- 第二种:
- 写在静态方法前面,这种表示对类对象加锁
- 第三种:
- 写在代码块中,锁是Synchonized括号里配置的对象(可能是实例对象,也可能是类对象)
总体说来就2种,一种就是锁实例对象,一种锁类对象。
锁实例对象就是当多个线程同时操作这个实例对象的时候必须先获取锁,如果无法获取锁,则必须处于等待状态,而和锁类对象区别是,当多个线程同时操作的时候,任何以这个类对象实例化的对象都要获取锁才能操作。举个简单例子
比如一个群人去打饭,只要是人就必须排队等待,一个个的打饭。不管是谁,但是吃完饭之后把盘子送回原地,但是这个时候不同的人可能吃饭快慢不同,但是肯定先吃饭后送盘子。现在写段代码我们比对一下。
public class RunnableTest implements Runnable { private synchronized void testSyncMethod() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getId() + "testSyncMethod:" + i); } } public void run() { testSyncMethod(); } public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(2); RunnableTest rt = new RunnableTest(); RunnableTest rt1 = new RunnableTest(); exec.execute(rt); exec.execute(rt1); exec.shutdown(); }
按照我们的理论输出结果肯定是无序排列的。如图
public class RunnableTest implements Runnable { private void testSyncBlock() { synchronized (RunnableTest.class) { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getId()+"testSyncBlock:" + i); } } } public void run() { testSyncBlock(); } public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(2); RunnableTest rt = new RunnableTest(); RunnableTest rt1 = new RunnableTest(); exec.execute(rt); exec.execute(rt1); exec.shutdown(); } }
而这段代码输入结果肯定是有序的。如下
那么我们在思考一个问题,如果类A有2个方法,如果我们在其中一个方法前面加入了synchronized,哪意味着我们别的线程调用这个类的另一个方法也需要获取锁才可以执行,也是另一个方法只是读,这样一来性能就大大的降低,所以我们在实际开发中尽量少在方法前加入synchronized,那么我们应该怎么做呢,既然是实际对象我们只需要加入一个类,锁定此类,只需要让类的一个方法进行锁定即可。ok下面代码如下
public class A { private Object obj="123"; public void a(){ synchronized (obj) { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread() + "a:" + i); } } } public void b(){ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread() + "b:" + i); } } } public class B implements Runnable{ private A a; public B(A a){ this.a=a; } public void run() { a.b(); } } public class C implements Runnable { private A a; public C(A a){ this.a=a; } public void run() { a.a(); } } public class E implements Runnable{ private A a; public E(A a){ this.a=a; } public void run() { a.a(); } } public class D { public static void main(String[] args) { A a=new A(); ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(new E((a))); executorService.execute(new B(a)); executorService.execute(new C(a)); executorService.shutdown(); } }
按照我们理论这段代码执行顺序是第一个线程和第二个线程无序,第三个线程必须等待第一个线程执行完毕才可以,测试结果也论证了我们的理论如下
二:wait、notify已经notifyAll
wait、notify、notifyAll是Object对象的属性,并不属于线程。我们先解释这三个的一个很重要的概念
wait:使持有该对象的线程把该对象的控制权交出去,然后处于等待状态(这句话很重要,也就是说当调用wait的时候会释放锁并处于等待的状态)
notify:通知某个正在等待这个对象的控制权的线程可以继续运行(这个就是获取锁,使自己的程序开始执行,最后通过notify同样去释放锁,并唤醒正在等待的线程)
notifyAll:会通知所有等待这个对象控制权的线程继续运行(和上面一样,只不过是唤醒所有等待的线程继续执行)
这个就好了,从上面的解释我们可以看出通过wait和notify可以做线程之间的通信,当A线程处理完毕通知B线程执行,B线程执行完毕以后A线程可以继续执行。ok我们使用例子来说明。
public class Temp { int count=0; public void waiter() throws InterruptedException { synchronized (this) { System.out.println("等待"); wait(); System.out.println(this.count); } } public void notifyer() throws InterruptedException { synchronized (this){ TimeUnit.SECONDS.sleep(1); System.out.println("唤醒"); for (int i=0;i<10;i++){ System.out.println(Thread.currentThread()+"notifyer:"+i); count+=i; } notify(); } } public class Waiter implements Runnable{ private Temp temp; public Waiter(Temp temp){ this.temp=temp; } public void run() { try { temp.waiter(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Notifyer implements Runnable{ private Temp temp; public Notifyer(Temp temp){ this.temp=temp; } public void run() { try { temp.notifyer(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Temp temp=new Temp(); ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(new Waiter(temp)); executorService.execute(new Notifyer(temp)); executorService.shutdown(); }
其中在notify中加入休眠1s目的是让线程waiter先执行更能看明白
我们在举一个例子,比如说我们经常提到的客户端请求和服务器响应,当客户端发送请求后就处于等待服务端的响应,而服务端会等待客户端端请求然后响应客户端请求,下面我们看看怎么去写代码
首先我们写一个对象Handler来专门处理客户端和服务端的,对于客户端有2个方法就是发送请求和等待服务端响应,对于服务端同样2个方法那就是等待客户端请求和响应客户端
public class Handler { private boolean isClientRequest=false; public void sendRequest(){ synchronized (this){ isClientRequest=true; this.notifyAll(); } } public void waitResponse() throws InterruptedException { synchronized (this){ while (isClientRequest){ this.wait(); } } } public void receiveRequest(){ synchronized (this) { isClientRequest = false; this.notifyAll(); } } public void waitRequest() throws InterruptedException { synchronized (this){ while (!isClientRequest){ this.wait(); } } } }
现在我们写客户端代码,客户端肯定先发送请求,但是先等待1s为了让服务端处于等待的效果,发送请求后就处于等待状态直到服务端的响应
public class Client implements Runnable { private Handler handler; public Client(Handler handler) { this.handler = handler; } public void run() { try { while (!Thread.interrupted()) { System.out.println("客户端发送请求"); TimeUnit.SECONDS.sleep(1); this.handler.sendRequest();//第二步 System.out.println("等待服务端的响应"); this.handler.waitResponse();//第三步 } } catch (InterruptedException e) { } System.out.println("客户端已经完成请求"); }
然后我们写服务端代码,服务端首先处于等待状态,收到客户端请求后立马进行处理,处理完毕之后再次等待客户端的请求
public class Server implements Runnable { public Handler handler; public Server(Handler handler) { this.handler = handler; } public void run() { try { while (!Thread.interrupted()) { System.out.println("等待客户端请求"); this.handler.waitRequest();//第一步 System.out.println("处理客户端请求"); TimeUnit.SECONDS.sleep(1); this.handler.receiveRequest();//第四步 } } catch (InterruptedException e) { } System.out.println("服务端处理已经完成"); } }
从上面我们预测肯定是等待客户端请求,发送请求,等待响应,处理客户端请求这样循环的结果。如下
在说一下wait和sleep的区别
区别1:在wait期间对象锁使释放的
区别2:可以通过notify和notifyAll,或者玲命令到期,从wait中恢复执行。如果wait不接受任何参数,这种wait将无线的等待下去,直到线程收到notify或notifyall的消息