线程高级应用-心得2-同步锁讲解及面试题案例分析
1.引入同步锁
2.同步锁案例分析
package com.itcast.family; public class TraditionalThreadSynchronized { public static void main(String[] args) { new TraditionalThreadSynchronized().init(); } // 该方法的作用是:外部类的静态方法不能实例化内部类对象;所以不能直接在外部类的main实例化,要创建一个中介的普通方法 private void init() { final Outputer outputer = new Outputer(); //线程1 new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } outputer.output("yangkai"); } } }).start(); //线程2 new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } outputer.output3("123456"); } } }).start(); } static class Outputer { //第一种使用锁的方法:部分代码块上锁 String xxx = ""; public void output(String name) { /*synchronized()锁中可以传参是任何的一个空串,结果同步了但是应该会有其他问题,执行结果: * yangkai 123456 123456 yangkai 123456 也可以是方法传进来的参数,比如这里name可以执行,但是结果会有问题;执行结果: y1a2n3g4k5a6 i yangkai 123456 所以一般都是用this,代表这个类本身,方法本身; 用了锁线程里的对象就要是同一个对象,否则会出错;比如上门代码用的都是同一个Outputer对象outputer, 要是将outputer换成new Outputer就会出错,因为是两个Outputer对象 */ synchronized(Outputer.class){ for (int i = 0; i < name.length(); i++) { //读取字符串内一个一个的字符 System.out.print(name.charAt(i)); } System.out.println(); } } //第二种使用锁的方法:整个方法上锁 //一般一个类中只使用一把锁,外面上了锁里面就不用上了,否则会出现死锁的状况 /*上面两个线程一个用的output()方法中的锁,一个用的是output2()方法中的锁, * 这样也可以互斥,达到同步的要求;因为他们用的是同一把门栓,即同一个对象; * 都是是Outputer对象,一个this一个直接是其对象中的方法 */ public synchronized void output2(String name) { for (int i = 0; i < name.length(); i++) { //读取字符串内一个一个的字符 System.out.print(name.charAt(i)); } System.out.println(); } //3.第三种静态锁的方法,这时需要内部类也是静态的 /*这时如果直接用第一种或第二种锁,不会与第三种形成互斥锁,因为静态方法与普通方法中的对象不是一个, * 这时只需要把第一种锁的中的this参数改成Outputer.class即可 */ public static synchronized void output3(String name) { for (int i = 0; i < name.length(); i++) { //读取字符串内一个一个的字符 System.out.print(name.charAt(i)); } System.out.println(); } } /* * 如果不使用线程锁synchronized会出现以下情况: * yangkai 123456 yangkai 1y2a3n4g5k6 ai */ } 3.同步锁面试题分析 package com.itcast.family; /** * 面试题: * 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次, * 接着再回到主线程又循环100次,如此循环50次,代码如下: * * 思路: * 编写一个业务类,是为了不把自己绕进去,体现代码的的高聚类特性和代码的健壮性, * 即共同数据(比如这里的同步锁)或共同算法的若干个方法都可以提到同一个类中编写 */ public class TraditionalThreadCommunication { public static void main(String[] args) { final Business business = new Business(); new Thread(new Runnable() { @Override public void run() { for(int i=1;i<=50;i++){ business.sub(i); } } }).start(); for(int i=1;i<=50;i++){ business.main(i); } } } //编写一个有子方法(用来调用子线程)和主方法(调用主线程)的业务类 class Business{ private boolean bShouldSub = true; public synchronized void sub(int i){ while(!bShouldSub){ try { //如果没轮到自己就等一会 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int j=1;j<=10;j++){ System.out.println("sub thread sequence of " + j + " ,loop of " + i); } bShouldSub = false; //唤醒下一个等待的线程 this.notify(); } public synchronized void main(int i){ /*这里最好用while,但是跟if的效果一样,只是前者代码更健壮, * while可以防止线程自己唤醒自己,即通常所说的伪唤醒; * 相当于一个人做梦不是被别人叫醒而是自己做噩梦突然惊醒; * 这时用while可以防止这种情况发生 */ while(bShouldSub){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int j=1;j<=100;j++){ System.out.println("main thread sequence of " + j + " ,loop of " + i); } bShouldSub = true; this.notify(); } }