1.资源竞争
并发的难点集中在如何对一个资源进行操作并且保证不出错,如果不采取任何措施,线程可不会乖乖按照你的想法进行执行,他们会产生一些非常奇葩的问题。
关键字:synchronized,同步关键字,可以修饰整个方法,也可以在方法内部使用同步块。它的作用,就是让多个线程在同时访问某个方法的时候,进行单一处理。每次只允许一个线程进入方法,进入后,对方法加锁,其他线程全部在方法外暂时堵塞,直到进入方法中的线程执行完毕之后,解锁,其他线程才允许进入。很好理解!
那如果有多个方法被修饰了synchronized呢?可以把多个synchronized修饰的方法想成一个同步组方法,他们就是一个整体,这一个组方法都只允许每次只有一个线程在执行。
那如果static方法被修饰了synchronized呢?很明显,那所有的线程对这个static方法都会被同步,而不单单只是对某一个对象了。如果有一个static同步方法和一个普通同步方法呢?要注意的是,他们不会被归为一个同步组中,即一个线程可以进入static同步方法中,而在同一时刻,另一个线程可以进入普通同步方法中。
2.同步代码块
synchronized修饰的方法,线程会被阻塞在方法外面,这样会造成一定程度上的性能损耗。其实很多时候,需要同步的部分只是方法中的某个部分,甚至就几行代码。所以我们可以把同步的规模进行缩小,缩小到只包含这一部分的代码。可以使用synchronized代码块来实现,synchronized代码块会接收一个参数,这个参数通常是某个对象,或者是this自身,其实他的含义就是需要获取谁的对象锁。通常在一个对象中使用synchronized代码块,参数一般设置成this,那么这把锁就是当前对象的锁,不过也有另外一种可能,比如两个同步方法,我需要有多个线程能分别同时进入。上面已经有说明了同一个对象中,synchronized方法组只能有一个线程进入,那怎么还能同时进入两个方法呢?synchronized代码块可以解决,秘诀就是这个参数的设置。要知道一个本质,同步都是根据对象的锁的进行的,不同的锁当然是可以给多个线程去执行的,所以synchronized(obj){}中的obj除了this,当然还可以自定义一个Object类的实例来充当,即在类中定义一个Object类的属性,把他设置为同步代码块的参数,OK,这样就不会和持有this锁的部分同步了。
以上全是理论,下面做点小例子测试。
public class JavaCurrent { public static void main(String[] args) { Target target = new Target(); for (int i = 0; i < 2; i++) { Thread t = new Thread(new Task(target)); t.start(); } Thread thread1 = new Thread(new Task1(target)); Thread thread2 = new Thread(new Task2(target)); thread1.start(); thread2.start(); } } ---------------------------------------------------------------------------------------- /** * 竞争目标类 * @author dlsm-syq * */ public class Target { private Object obj = new Object();//新建加锁对象 public synchronized static void methodOfStatic(){ System.out.println(Thread.currentThread().getName()+"->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " methodOfStatic 处理完成,期间没有其他线程打扰"); } public void methodOfNormalBlock(){ synchronized (this) { System.out.println(Thread.currentThread().getName() + "-->进入methodOfNormalBlock方法代码块中,处理事物需要3秒钟"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " methodOfNormalBlock 处理完成,期间没有其他线程打扰"); } } public void methodOfNormalBlockObj(){ synchronized (obj) { System.out.println(Thread.currentThread().getName() + "-->进入methodOfNormalBlockObj方法代码块中,处理事物需要3秒钟"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " methodOfNormalBlockObj 处理完成,期间没有其他线程打扰"); } } } ----------------------------------------------------------------------------------------- public class Task implements Runnable{ private Target target; public Task(Target target) { this.target = target; } @Override public void run() { Target.methodOfStatic(); } } public class Task1 implements Runnable{ private Target target; public Task1(Target target) { this.target = target; } @Override public void run() { target.methodOfNormalBlock(); } } public class Task2 implements Runnable{ private Target target; public Task2(Target target) { this.target = target; } @Override public void run() { target.methodOfNormalBlockObj(); } } ----------------------------------------------执行结果------------------------------------- Thread-0->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间 Thread-3-->进入methodOfNormalBlockObj方法代码块中,处理事物需要3秒钟 Thread-2-->进入methodOfNormalBlock方法代码块中,处理事物需要3秒钟 Thread-0 methodOfStatic 处理完成,期间没有其他线程打扰 Thread-2 methodOfNormalBlock 处理完成,期间没有其他线程打扰 Thread-1->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间 Thread-3 methodOfNormalBlockObj 处理完成,期间没有其他线程打扰 Thread-1 methodOfStatic 处理完成,期间没有其他线程打扰
貌似没有问题
3.wait(),notify(),notifyall()
先说说对象锁。一个线程都是在进入synchronized方法或者代码块中才能获取该对象的锁,在执行完毕之前,都会一直持有锁。所以这里一个非常重要的注意点就是线程必须持有锁,也就是说必须在synchronized方法或者代码块内部情况下,才能继续话题。
wait(),notify(),notifyall() 三个方法都不是线程相关类中的方法,而是原始类Object中的方法,原因很简单了,就是每个对象都有控制线程的权利。三个方法都必须在synchronized方法或者代码块中使用,不然必定抛出java.lang.IllegalMonitorStateException异常。要注意是调用他们三个的时候,调用notify的对象必须要和synchronized的锁对象保持一致,不然也是IllegalMonitorStateException异常。也许有点混乱,这里举个例子说明:
public class JavaCurrent { public static void main(String[] args) { B b = new B(); A a = new A(b); Thread t1 = new Thread(new ATask(a)); Thread t2 = new Thread(new BTask(b)); t2.start(); try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t1.start(); } } class ATask implements Runnable{ private A a; public ATask(A a) { this.a = a; } @Override public void run() { a.amethod(); } } class BTask implements Runnable{ private B b; public BTask(B b) { this.b = b; } @Override public void run() { for (int i = 0; i < 10; i++) { b.bmethod(); } } } ------------------------------------------------------------------------------ public class A { private B b; public A(B b) { this.b = b; } public synchronized void amethod(){ System.out.println(Thread.currentThread().getName()+" in amethod doing something"); b.notifyAll(); } } --------------------------------------------------------------------------------- public class B { public synchronized void bmethod(){ System.out.println(Thread.currentThread().getName() +" in bmethod doing somethind"); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } 运行结果: Thread-1 in bmethod doing somethind Thread-0 in amethod doing something Exception in thread "Thread-0" java.lang.IllegalMonitorStateException at java.lang.Object.notifyAll(Native Method) at syq.think.current7.p70.A.amethod(A.java:13) at syq.think.current7.p70.ATask.run(JavaCurrent.java:60) at java.lang.Thread.run(Unknown Source)
例子中,A类中持有一个B类的对象引用,然后在synchronized方法中调用b.notifyall()方法,结果出现异常。也就是说没有获取b的对象锁,但却是在a的synchronized方法中。所以调用notifyall的对象所处的同步块要在该对象的synchronized块中。
wait()和sleep()的区别,sleep()可以在任何地方使用,wait()必须在同步块中使用,wait()会使当前线程释放对象锁,然后进入一个waitting room的线程等候区,等待notify。notify()会挑waitting room中的一条线程进行唤醒,notifyall()唤醒waitting room中的所有线程,然后线程都去竞争对象锁,建议使用notifyall方法。
接下来看看具体用法的例子:
public class JavaCurrent { public static void main(String[] args) { //开8个线程去跑WaitTask Target target = new Target(); for (int i = 0; i < 8; i++) { Thread t = new Thread(new WaitTask(target)); t.start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //开1个线程去跑NotifyTask Thread thread = new Thread(new NotifyTask(target)); thread.start(); } } ---------------------------------------------------------------------------------- public class Target { private int allowIn = 3;//允许4个线程经过setwait() private int count = 0; public void setWait(){ System.out.println(Thread.currentThread().getName()+ " 进入setWait"); synchronized (this) { System.out.println(Thread.currentThread().getName()+ " 进入setWait中的同步块中"); if(count > allowIn){ try { System.out.println(Thread.currentThread().getName()+ " 发现已经超出最大数值了,只能等待了!"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { TimeUnit.SECONDS.sleep(1);//休眠1秒 System.out.println(Thread.currentThread().getName()+ " 处理setWait完毕,退出方法,释放锁"); count ++ ; } catch (InterruptedException e) { e.printStackTrace(); } } } public void setNotify(){ System.out.println(Thread.currentThread().getName()+ " 进入setNotify"); synchronized (this) { System.out.println(Thread.currentThread().getName()+ " 进入setNotify中的同步块中"); if(count > allowIn){ System.out.println(Thread.currentThread().getName()+ " 发现已经超出最大数值了,数值清理!"); count = 0; notifyAll(); }else{ try { TimeUnit.SECONDS.sleep(2);//休眠2秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ " 处理setNotify完毕,退出方法,释放锁"); } } } } ------------------------------------------------------------------------------------ public class NotifyTask implements Runnable{ private Target target; public NotifyTask(Target target) { this.target = target; } @Override public void run() { while(true){ target.setNotify(); } } } public class WaitTask implements Runnable{ private Target target; public WaitTask(Target target) { this.target = target; } @Override public void run() { target.setWait(); } } 执行结果: Thread-0 进入setWait Thread-2 进入setWait Thread-1 进入setWait Thread-0 进入setWait中的同步块中 Thread-3 进入setWait Thread-5 进入setWait Thread-7 进入setWait Thread-4 进入setWait Thread-6 进入setWait Thread-0 处理setWait完毕,退出方法,释放锁 Thread-6 进入setWait中的同步块中 Thread-6 处理setWait完毕,退出方法,释放锁 Thread-4 进入setWait中的同步块中 Thread-8 进入setNotify Thread-4 处理setWait完毕,退出方法,释放锁 Thread-2 进入setWait中的同步块中 Thread-2 处理setWait完毕,退出方法,释放锁 Thread-7 进入setWait中的同步块中 Thread-7 发现已经超出最大数值了,只能等待了! Thread-5 进入setWait中的同步块中 Thread-5 发现已经超出最大数值了,只能等待了! Thread-3 进入setWait中的同步块中 Thread-3 发现已经超出最大数值了,只能等待了! Thread-1 进入setWait中的同步块中 Thread-1 发现已经超出最大数值了,只能等待了! Thread-8 进入setNotify中的同步块中 Thread-8 发现已经超出最大数值了,数值清理! Thread-8 进入setNotify Thread-1 处理setWait完毕,退出方法,释放锁 Thread-3 处理setWait完毕,退出方法,释放锁 Thread-5 处理setWait完毕,退出方法,释放锁 Thread-7 处理setWait完毕,退出方法,释放锁 Thread-8 进入setNotify中的同步块中 Thread-8 发现已经超出最大数值了,数值清理! Thread-8 进入setNotify Thread-8 进入setNotify中的同步块中 Thread-8 处理setNotify完毕,退出方法,释放锁 Thread-8 进入setNotify Thread-8 进入setNotify中的同步块中
可以看到同时开了8个线程跑waitTask,当thread0进入同步块之后,其他线程都在外面等待了,知道它处理完毕释放锁了,thread6进入了同步块中进行相同的处理。但thread7进入时,因为前面已经有thread0,6,4,2这四个线程通过了,所以thread7只能wait等待了,同理后面的进入的线程全部都要去wait等待。最后当thread8进入setNotify并执行完毕之后,重置了count属性,并且notifyall所有wait线程,后续的线程又可以继续活动了。注意:被唤醒之后的线程会重新竞争对象锁,然后从wait()处继续向下执行。