13.烧水泡茶

 public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread() {public void run() {System.out.println("洗水壶");try {sleep(1);} catch  (InterruptedException e) {e.printStackTrace();}System.out.println("烧开水");try {sleep(15);} catch (InterruptedException e) {e.printStackTrace();}}};Thread t2 = new Thread() {public void run() {System.out.println("洗茶壶");try {sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("洗茶杯");try {sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("拿茶叶");try {sleep(1);} catch (InterruptedException e) {e.printStackTrace();}try {t1.join();//等待t1执行完,等水烧好.} catch (InterruptedException e) {e.printStackTrace();}System.out.println("pao cha");}};t1.start();t2.start();}

这种接法的缺陷:

上面的解法是等t1水烧开后,t2泡茶,如果反过来要实现t1等t2拿过来的茶叶,由t1来进行泡茶呢? 没有满足第二种情况.

上面的线程各自执行,如果要模拟t1把水壶给t2泡茶,或者模拟t2把茶叶交给t1泡茶.

后面要学习的内容有:

  • 共享问题.
  • synchronized
  • 线程安全分析
  • Monitor
  • wait/notify
  • 线程状态转换
  • 活跃性
  • Lock

14.临界区

  • 一个程序运行多个线程本身是没有问题的.
  • 问题出在多个线程访问共享资源
  • 多个线程读共享资源其实也没有问题.
  • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题.
  • 一段代码块内如果存在对共享资源的多线程读写操作,这称这段代码块为临界区.

14.1竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件.

例如:t1,t2线程交替执行,有可能会导致结果不正确.

static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread() {public void run() {for(int i=0;i<5000;++i) {count++;}}};Thread t2=new Thread() {public void run() {for(int i=0;i<5000;++i) {count--;}}};t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

14.2synchronized解决方案

为了避免临界区的竞态条件发生,有多种手段可以达到目的.

  • 阻塞式的解决方案,synchronized,Lock.
  • 非阻塞式的解决方案:原子变量.

本次课使用阻塞式的解决方案,:synchronized,来解决以上问题,即俗称的[对象锁],它采用了互斥的方式让同一时刻至多只有一个线程能持有[对象锁].其他线程再想获取这个[对象锁]时就会阻塞住,这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换.

synchronized语法:

synchronized(对象){   临界区    }

14.3关于synchronized的思考

synchronized实际上是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的.不会被线程的切换所打断,

为了加深理解.请细品一下3个问题.

  • 如果把synchronized(obj)放在for循环之外呢,如何理解?
         Thread t1=new Thread() {public void run() {synchronized (Lock) {for(int i=0;i<5000;++i)count++;}}};

synchronized的作用就是使得保护的临界区原子化,如果synchronized放在for的外面,那么就意味着for循环内的代码具有了原子性,在执行不完for中的代码前,是不会解锁的. ----强调原子性

  • 如果t1 synchronized(obj1)而t2 synchronized(obj2) 会怎样运作?

对象锁不同,要保护共享资源需要多个线程锁住的是同一个对象,--强调锁对象

  • 如果t1 synchronized(obj) 而t2并没有加锁,会怎么样?

t2如果不加锁,等于没有限制,,就不会去获取锁对象,也就不会被阻塞(Blocked),就可以所以的操作共享资源.

14.4锁对象---面向对象改进

public class Test15 {static int count=0;//static Object Lock = new Object();public static void main(String[] args) throws InterruptedException {Room room = new Room();Thread t1=new Thread() {public void run() {for(int i=0;i<5000;++i) {room.increament();}}};Thread t2=new Thread() {public void run() {for(int i=0;i<5000;++i) {room.decreament();}}};t1.start();t2.start();t1.join();t2.join();System.out.println(room.getCount());}}class Room{private int count=0;public void increament() {synchronized (this) {count++;}}public void decreament() {synchronized (this) {count--;}}public int getCount() {synchronized (this) {return count;}}
}

14.5方法上的synchronized

加在非静态方法上的synchronized

    public synchronized void test() {}

等价于

   public void test1() {synchronized (this) {}}

加在静态方法上的synchronized

    public synchronized static void test2() {}

等价于

    public static void test2() {synchronized (Test16.class) {}}

注意:synchronized加在了非静态方法上了,并不是意味着将方法锁住了,而是方法里面的对象(this)

加在了静态方法上等价于是锁住了类对象.

不加synchronized的方法就好比不遵守规则的人,不老实排队(去翻窗子进去)

15.线程八锁.

其实主要考察的就是synchronized锁住的是哪个对象?

如果是相同的对象则会存在互斥,.如果是不同的对象则不存在互斥,直接按照线程并发执行处理..

15.1第一锁.

public class Lock1 {public static void main(String[] args) {Number n1 = new Number();//启动线程1new Thread() {public void run() {n1.a();}}.start();//启动线程2 new Thread() {public void run() {n1.b();}}.start();;}
}class Number{public synchronized void a() {System.out.println("1");}public synchronized void b() {System.out.println("2");}
}
  • 分析:首先synchronized锁的是Number.class的成员方法:a(),b();查看锁对象是不是this.
  • 查看:两个线程都是通过n1来进行调用的,-->表明this相同(即当前的锁对象相同)---->>是存在互斥关系的.
  • 因此:CPU调度先调度哪个线程,哪个线程就先执行.
  • 执行结果:有两种情况
  • 12(因为第一个线程先启动,所以出现,12的概率大.)或者21

15.2第二锁

public class Lock2 {public static void main(String[] args) {Number2 n1 = new Number2();//启动线程1new Thread() {public void run() {System.out.println("线程1启动");try {sleep(1);} catch (InterruptedException e) {e.printStackTrace();}n1.a();}}.start();//启动线程2 new Thread() {public void run() {System.out.println("线程2启动");n1.b();}}.start();;}
}class Number2{public synchronized void a() {System.out.println("1");}public synchronized void b() {System.out.println("2");}
}
  • 分析:首先synchronized锁的是Number2.class的成员方法:a(),b();查看锁对象是不是this.
  • 查看:两个线程都是通过n1来进行调用的,-->表明this相同(即当前的锁对象相同)---->>是存在互斥关系的.
  • 因此:CPU调度先调度哪个线程,哪个线程就先执行.
  • 执行结果:有两种情况
  • 12(因为第一个线程先启动,所以出现,12的概率大.)或者21
  • sleep()方法并不能释放锁.

15.3第三锁

public class Lock3 {public static void main(String[] args) {Number3 n1 = new Number3();//启动线程1new Thread() {public void run() {System.out.println("线程1启动");try {sleep(1);} catch (InterruptedException e) {e.printStackTrace();}n1.a();}}.start();//启动线程2 new Thread() {public void run() {System.out.println("线程2启动");n1.b();}}.start();//启动线程3new Thread() {public void run() {System.out.println("线程3启动");n1.c();}}.start();}
}class Number3{public synchronized void a() {System.out.println("1");}public synchronized void b() {System.out.println("2");}public  void c() {System.out.println("3");}
}
  • 分析:首先synchronized锁的是Number3.class的成员方法:a(),b();查看锁对象是不是this   c()方法无锁.
  • 查看:两个线程都是通过n1来进行调用的,-->表明this相同(即当前的锁对象相同)---->>是存在互斥关系的.但是线程3没有锁.
  • condition1:如果线程1首先拿到了时间片,t2阻塞,t2在等待t1释放锁.线程1进入一秒的睡眠后,因为线程3并没有加锁,线程3可以就可以争取到了时间片,进行输出.
  • 结果:3 12
  • condition2:如果是线程2首先拿到了时间片,线程1会阻塞,但是线程3无锁,那么意味着,线程3可以和线程2竞争使用权,
  1. 线程2拿到使用权并输出,然后是线程3,线程2执行完毕,无论是线程3还是线程1抢到时间片,但是因为线程1会休眠,所以线程3总是在线程1之前输出的.即:321,231.

15.4第四锁

public class Lock4 {public static void main(String[] args) {Number4 n1 = new Number4();Number4 n2 = new Number4();//启动线程1new Thread() {public void run() {System.out.println("线程1启动");try {sleep(1);} catch (InterruptedException e) {e.printStackTrace();}n1.a();}}.start();//启动线程2 new Thread() {public void run() {System.out.println("线程2启动");n2.b();}}.start();}
}class Number4{public synchronized void a() {System.out.println("1");}public synchronized void b() {System.out.println("2");}
}
  • 分析:首先两个线程获取到的对象锁都不是同一个,即不存在冲突问题.按照普通并发处理.
  • 因为线程1在输出前总要睡眠一秒.所以输出结果:总是.21.

15.5第五锁

public class Lock5 {public static void main(String[] args) {Number5 n1 = new Number5();//线程1new Thread() { public void run() {try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}}.start();//线程2new Thread() { public void run() {n1.b();}}.start();;}
}class Number5{public static synchronized void a() throws InterruptedException {Thread.sleep(1000);System.out.println("1");}public synchronized void b() {System.out.println("2");}
}
  • 分析:因为两个线程一直执行的是静态方法,一个是成员方法.锁对象不同,因此无互斥.
  • 输出:2 1

15.6第六锁

public class Lock6 {public static void main(String[] args) {Number6 n1 = new Number6();//线程1new Thread() { public void run() {try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}}.start();//线程2new Thread() { public void run() {n1.b();}}.start();;}
}class Number6{public static synchronized void a() throws InterruptedException {Thread.sleep(1000);System.out.println("1");}public static synchronized void b() {System.out.println("2");}
}
  • 分析:首先synchronized锁住的都是两个静态方法.并且锁住的类对象都是一样的.都是Number6.class,因此存在互斥.
  • 结果:1 2或者2 1

15.7第七锁

public class Lock7 {public static void main(String[] args) {Number7 n1 = new Number7();Number7 n2 = new Number7();//线程1new Thread() { public void run() {try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}}.start();//线程2new Thread() { public void run() {n2.b();}}.start();;}
}class Number7{public static synchronized void a() throws InterruptedException {Thread.sleep(1000);System.out.println("1");}public   synchronized void b() {System.out.println("2");}
}
  • 分析:首先synchronized分别锁住的是静态方法和成员方法.锁对象不同,一个是 类对象,一个是this(n2对象) 因此不存在互斥.
  • 结果:2 1

15.8第八锁

public class Lock8 {public static void main(String[] args) {Number8 n1 = new Number8();Number8 n2 = new Number8();//线程1new Thread() { public void run() {try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}}.start();//线程2new Thread() { public void run() {n2.b();}}.start();;}
}class Number8{public static synchronized void a() throws InterruptedException {Thread.sleep(1000);System.out.println("1");}public  static synchronized void b() {System.out.println("2");}
}
  • 分析:synchronized锁住的是两个静态方法,这两个静态方法都输出Number8.class的对象.因此是相同的类变量.因此存在互斥
  • 结果:1 2或者2 1

16.变量的线程安全分析

16.1成员变量和静态变量是否线程安全?

  • 如果它们没有被共享,则是线程安全的.
  • 如果他们被共享了.根据它们的状态能否被改变又分为两种情况.
  • 如果只有读操作,则线程安全.(因为读操作不影响,内存中的值.)....但是会出现脏读
  • 如果有读写操作.,则这段代码的临界区,需要考虑线程安全了.

16.2局部变量是否线程安全?

  • 局部变量是线程安全的.
  • 但局部变量引用的对象则未必,
  • 如果该对象没有逃离方法的作用访问,它是线程安全的.
  • 如果该对象逃离方法的作用范围,需要考虑线程安全.

16.2.1局部变量线程安全分析.

     public static void test1() {int i = 10;i++;}
  • 每个线程在调用test1()时,局部变量i,会在每个线程的桢栈内存中会被创建多份,因此不存在共享
  • 局部变量实现线程私有的.(不共享就安全)

16.2.1局部变量引用的安全分析

public class TestThreadSafe {static final int THREAD_NUMBER=2;static final int LOOP_NUMBER=200;public static void main(String[] args) {ThreadUnsafe test = new ThreadUnsafe();for(int i=0;i<THREAD_NUMBER;i++) {new Thread() {public void run() {test.method1(LOOP_NUMBER);}}.start();}}
}class ThreadUnsafe{ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {for(int i = 0 ; i <loopNumber ;i++) {method2();method3();}}private void method2() {list.add("1");}private void method3() {list.remove(0);}
}
  • 有时候会出现:Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: -1
  • at java.util.ArrayList.add(ArrayList.java:459)
  • 分析:无论在哪一个线程中,method2引用的都是同一个对象中的list成员变量.(共享了)

将list修改为局部变量.代码如下

class ThreadSafe{public final void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for(int i =0;i<loopNumber;i++) {method2(list);method3(list);}}public void method2(ArrayList<String> list) {list.add("1");}public void method3(ArrayList<String> list) {list.remove(0);}
}
  • 分析:list是局部变量,每个线程调用时会创建其不同实例,没有共享.
  • 而method2的参数是从method1中传递过来的,与method1中引用同一个对象,
  • method3的参数分析与method2相同

方法访问修饰符带来的思考,如果把method2和method3的方法修改为public会不会出现线程安全问题?

  • 情况1:有其它线程调用method1和method2

    • method1,2有可能被其他线程调用,,比如线程1调用method1,线程2调用method2,因为线程1中的list是局部变量,私有的,线程2调用method2需要传入一个list,那么就是另外一个不同于method1中的list了.因此不存在线程安全问题.(各玩各的没事)
  • 情况2:在情况1的基础上,为ThreadSafe类添加子类,子类覆盖 method2或method3方法
    • 子类复写了method3并在其中又开启了一个线程,这就有问题了,这等于是两个线程玩同一个list.有可能会出事的.
class ThreadSafeSubClass extends ThreadSafe{@Overridepublic void method3(ArrayList<String> list){new Thread() {public void run() {list.remove(0); }}.start();}
}
  • 因此我们得出:方法的访问修饰符是有意义的,private,final使得子类不能影响父类的行为.在一定程度上加强了线程安全性.
  • 从这个例子中可以看出private  和final 提供的[安全]的意义所在,细品开闭原则中的[闭];

17.常见的线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.until.concurrent包下的类

17.1常见类的组合调用

这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法,是线程安全的.也可以理解为:

  • 它们的每个方法是原子的.
  • 但注意它们多个方法的组合不是原子的.见后分析.
  • public static void main(String[] args) throws InterruptedException {Hashtable<String,String> table = new Hashtable<String, String>();//线程1Thread t1 = new Thread() {public void run() {if(table.get("key") == null) {table.put("key", "t1");}}};//线程2Thread t2 = new Thread() {public void run() {if(table.get("key") == null) {table.put("key", "t2");}}};t1.start();t2.start();t1.join();t2.join();System.out.println(table.get("key"));}

    分析:

17.2常见类-不可变.

String,Integer等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的.

内部状态不可变?那String.class里面有substring方法,replace方法.

分析:查看了String中substring方法,发现它并没有改变当前字符串,而是新建了一个新的String对象,在原有字符串的基础之上进行复制,截取的.

 public String(char value[], int offset, int count) {if (offset < 0) {throw new StringIndexOutOfBoundsException(offset);}if (count <= 0) {if (count < 0) {throw new StringIndexOutOfBoundsException(count);}if (offset <= value.length) {this.value = "".value;return;}}// Note: offset or count might be near -1>>>1.if (offset > value.length - count) {throw new StringIndexOutOfBoundsException(offset + count);}//在原有字符串上进行复制.截取的this.value = Arrays.copyOfRange(value, offset, offset+count);}

replace也是一样的机制.

实例分析:

//是否线程安全?Map<String,Object> map = new HashMap<>();//不安全String s1 = "...";final String s2 = "...";Date d1 = new Date();final Date d2 = new Date();public void doGet(HttpServletRequest req,HttpServletResponse res) {//使用上述变量}
  • Map不是线程安全的,Hashtable是线程安全的.
  • String是线程安全的.Date不是线程安全的.
  • final Date d2只能确保.d2的引用不会发生改变,但是日期内部的属性是可以发生修改的.(存在读写操作);

例2:

public abstract class Test19 {public void bar() {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");foo(sdf);}public abstract  void foo(SimpleDateFormat sdf);public static void main(String[] args) {new Test19().bar();}
}
  • 分析:因为foo的行为不确定,可能导致不安全发生,这种情况被称为外星方法.
  • 在复写父类foo中开启另一个线程,操作
  • String为什么是final的.
  • 这是为了避免子类继承String,因为有些子类覆盖父类的方法后,可能会出现线程不安全的情况,

17.3购票的例子。

public class Test1 {public static void main(String[] args) throws InterruptedException {TicketWindow window = new TicketWindow(10000);//卖出的票数统计List<Integer> amountList = new Vector<>();//所有线程的集合List<Thread> threadList = new ArrayList<>();// 开始卖票for (int i = 0; i < 1000; i++) {Thread thread = new Thread() {public void run() {int sellNumber=window.sell(getRandom());amountList.add(sellNumber);}// run-end};threadList.add(thread);thread.start();} // for-end//等待所有线程执行完毕。for(Thread thread:threadList) thread.join();//卖出去的票数System.out.println("余票:"+window.getCount());System.out.println("卖出去的票数:"+amountList.stream().mapToInt(i->i).sum());}// main-end// Random为线程安全的static Random random = new Random();public static int getRandom() {return random.nextInt(5) + 1;}
}    class TicketWindow {private int count;//构造器public TicketWindow(int count) {this.count=count;}//获得当前票数public int getCount() {return this.count;}//售票public int sell(int count) {if(this.count>=count) {this.count-=count;return count;}//不够买或者卖完了.else  return 0;}
}

threadList是属于mian线程的,不会被多个线程调用到,因此是线程安全的.

两个不一样的对象调用的组合,如果单个都是线程安全的,组合到一起也是线程安全的.

对照hashtable的put,get.因为hashtable的调用对象都是同一个对象,尽管,它的put,get都是线程安全的,但是组合起来使用会出现不安全的情况.

17.4转账问题

public class ExerciseaTransfer {static Random random = new Random();public static int randomAmount() {return random.nextInt(100)+1;}public static void main(String[] args) throws InterruptedException {Account a = new Account(1000);Account b=new Account(1000);Thread t1 = new Thread() {public void run() {for(int i=0;i<1000;i++) {a.transfer(b,randomAmount());}}};Thread t2 =new Thread() {public void run() {for(int i=0;i<1000;i++) {b.transfer(a, randomAmount());}}};t1.start();t2.start();t1.join();t2.join();System.out.println("A账户的钱:"+a.getMoney());System.out.println("B账户的钱:"+b.getMoney());}
}//用户类
public class Account {private int money;public Account(int money) { this.money=money;}public int getMoney() {return this.money;}public void setMoney(int money) {this.money=money;}//转账public   void transfer(Account targer,int amount) {synchronized(Account.class) {if(this.money>=amount) {this.setMoney(this.money-amount);targer.setMoney(targer.getMoney()+amount);}}}
}
  • 分析:线程不安全一定会出现在transfer方法内.(因为里面做了读写操作),
  • 解决办法:1.将两个类都添加上锁.(不可行:因为这样有可能会导致死锁)
  • 2.提取通性,对Account.class进行加锁.(但是效率不高.只能对两个账户进行操作,其他的用户转账只能等待)

18.Monitor概念

18.1Java对象头-  也可以理解为计网中的数据报的首部信息

以32位虚拟机为例

  • 普通对象:   Object Header(64bit)=Mark Work(32bit)+Klass Word(32bit)
  • 数组对象: Object Header(96bit) = Mark Work(32bit)+Klass Word(32bit) + Array Length(32bit)
  • Mark Word 主要是用来
  • Klass Word只要是用来找到该对象的类对象的.

18.2Monitor(锁或者叫做管程)

每个Java对象都可以关联一个Monitor对象.如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置为指向Monitor对象的指针.

Monitor结构如下:

  • 刚开始Monitor中的Owner为null;
  • 当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner设置为Thread-2,Monitor中有且只能有一个Owner
  • 在Thread-2上锁的过程中,如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),就会进入EntryList中 阻塞.
  • Thread-2执行完同步代码块中的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争时是非公平的.(即先来不一定先得)
  • 图中WaitSet中的Thread-0,Thread-1是之前获得过锁的,但条件不满足进入WAITING状态的线程.
  • 注意
    • synchronized必须是进入同一个对象的monitor中才会上述保护效果.
    • 不加synchronized的对象不会关联监视器,自然不会遵从上述规则.

19.Synchronized原理の进阶

19.1轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多个线程访问,但多线程访问的时间是错开的.(即没有出现竞争),那么可以使用轻量级锁来进行优化.

轻量级锁对使用者是透明的,即语法仍为:synchronized(...){...}

假设有两个方法同步块,利用同一个对象加锁,

        static final Object obj= new Object();public static void method1(){synchronized(obj){...               method2();}}public static void method2(){synchronized(obj){....}}
  • 创建锁记录(Lock Record)对象,每个线程的桢栈中都会包含一个锁记录的结构体,内部可以存储锁定对象的Mark Word.
  • 让锁记录中Object refrenence指向锁对象,并尝试用cas替换Object 的Mark Word ,将Mark Word的值存入锁记录中.
  • 如果cas替换成功,对象头中存贮了锁记录地址和状态00,表示由该线程给对象加锁,这时图示如下:
  • 如果cas失败有两种情况:
    • 如果是其他线程已经持有了该对象的轻量级锁,这时表明有竞争.进入锁膨胀过程
    • 如果是自己执行了synchronized锁重入,那么再添一条Lock Record作为重入的计数.只不过重入的所记录的MarkWord为null.
    • 当退出synchronized代码块(解锁时)如果有取值为null的记录,表示有重入,这时重置锁记录,表示重入计数减一;
    • 当退出synchronized代码块时的锁记录不为空,这时使用cas将Mark Word的值回复给对象头
      • 成功,解锁成功
      • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,那就进入了重量级锁的解锁流程.
        • 轻量级锁没有阻塞功能,因此转入重量级锁中就包含了阻塞功能(EntryList)这也可能是出于这个原因
  • 这时Thread-1线程加轻量级锁失败了,接着进入了锁膨胀状态.
    • 即为Object对象申请Monitor锁,让Object指向重量级锁地址.
    • 然后自己进入Monitor的EntryList中阻塞,进行等待.
  • 当Thread-0退出同步块解锁时,使用cas将MarkWord的值回复给对象头,结果失败了.因为此时的锁已经升级为重量级锁,会进入重量级锁的解锁流程.即按照Monitor地址找到Monitor对象,设置Ower为空,并且唤醒EntryList中的阻塞的线程.

19.2自旋优化

重量级锁在竞争时,还可以使用自旋来进行优化,如果当前线程自旋成功,(即这时持锁线程已经退出了同步块释放了锁.),这时可以避免阻塞.但是如果自旋重试仍然没有获得资源也会进行阻塞.

通俗的讲:别人再用,你也想用,但是一个时刻只能一个人用,所以你会在等的过程中过去看看用完没.如果刚好用完了,你无缝衔接.节约了整体的时间.如果过去看的次数到达一定的闸值,等的人就不过去看了.乖乖的在原地等(阻塞).

在Java6之后自旋锁变成了自适应的,比如对象最近一次自旋成功,那么它就会认为目前的条件适合自旋,它就会多自旋几次.反之,就会少自旋甚至不自旋.

此外,自旋的话或占用Cpu,单核条件下自旋等于浪费时间.多核条件下才能发挥其优势.

java7之后,不能控制自旋的开关了.

19.3偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行cas操作.

Java6中引入了偏向锁来进一步优化:只有第一次使用cas将线程ID设置到MarkdWord中,之后发现这个线程ID是自己就表示没有竞争.不用重新Cas操作.以后只要不发生竞争.这个对象就归该线程所有.

轻量级锁:

偏向锁:

对比:偏向锁省去了Cas操作,只需要一个if判断即可.

当一个对象被创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,MardWord值的最后三位:101,其中第一个1代表启用偏向锁,01表示无锁状态.并且此时的,thread,epoch,age都为0
  • 偏向锁默认是延迟的,不会在程序启动时立刻生效,如果想立刻生效,可以添加JVM参数, -XX:BiasedLockingStartupDelay=0来去掉时延.
  • 如果没有开启偏向锁,那么对象创建后,markword最后3位为:001,hashcode,age都为0,
  • 处于偏向锁的对象解锁之后,线程id仍存储于对象头中.
  • 禁用偏向锁的JVM参数: -XX :-UseBiasedLocking禁用偏向锁
  • 偏向锁是优化后的轻量级锁,可以减少一次cas操作,提高性能.
  • 调用hashcode会使得偏向锁禁用,变成不可偏向的.
  • 优先级:有偏向锁会用偏向锁,如果有其他线程使用了锁对象会变为轻量级锁,如果发生竞争会升级为重量级锁.

19.3.1撤销偏向锁--调用对象的HashCode

调用了对象的HashCode,但偏向锁的对象MarkWord中存储的是线程ID,如果调用对象的Hashcode会导致偏向锁的线程ID被覆盖,即偏向锁被撤销了.

  • 轻量级锁会在锁记录(Lock Record)中记录hashCode
  • 重量级锁会在Monitor中记录hashCode

19.3.2撤销偏向锁---其他线程使用对象

当有其他线程要使用偏向锁对象时,会将偏向锁升级为轻量级锁,

偏性锁:本来我这个是个t1线程专用的,这又来了一个,使得我来回偏向比较累(耗费Cpu)..那就换锁(轻量级锁)呗.

19.3.3撤销偏向锁---调用wait/notify方法.

因为wait和notify方法是唤醒阻塞线程用的,那就需要EntryList.它们两个是重量级锁才拥有的特性.

19.4批量重偏向

如果对象被多个线程访问,但是没有发生竞争,这时偏向了线程T1的对象仍然有机会重新偏向T2,重偏向后会重置对象的Thread-ID

当撤销偏向锁的次数超过20次后,jvm会觉得我是不是偏向错了,于是会再给这些对象加锁时重新偏向至加锁线程.

19.5批量撤销

当撤销偏向锁闸值超过40次后,jvm会觉得自己确实偏向错了,于是将整个类所有的对象设为不可偏向.新建的对象也不可偏向.

20.锁消除

static int x=0;
public void a(){x++;}public void b(){Object o = new Object();synchronized(o){x++;}
}

逃逸分析,b方法中的synchronized根本不会逃离方法体中,因此不会被共享.所以这个同步锁也就没有必要了,Jvm对其进行了优化在实际执行过程中去除了锁..因此a,b方法的耗费时间几乎一致.

21.wait/notify

  • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态.
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片.
  • BLOCKED线程会在Owner线程释放锁时唤醒.
  • WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争锁.

21.1 API的介绍

  • obj.wait()让进入object监视器的线程到WaitSet中等待(会释放锁,离开Owner)
  • obj.notify(),在object上正在WaitSet等待的线程中挑一个唤醒(随机).
  • obj.notifyAll(),唤醒所有在object上正在WaitSet中等待的线程.
  • 它们都是线程之间进行协作的手段,都属于Object对象的方法.必须获得此对象的锁,才能调用这几个方法.

问题:wait和sleep的区别?

wait会释放掉Owner(锁),而sleep不会释放Owner(锁).

  • 带参数的wait(int n)方法. n的作用就是等n毫秒后自动唤醒.也可以被notify/notifyAll提前唤醒.
  • wait();实际上就是在内部又调用了一次wait(0); //0代表一直等,直到被notify/notifyAll唤醒.

22.wait/notify的正确姿势

sleep(long n)和wait(long n)的区别

  1. sleep是Thread的静态方法,而wait是Object的方法.(从API来源の异)
  2. sleep不需要强制和synchronized配合使用,但wait必需要和synchronized一起用.因为wait只有Owner才能调用.必需先要持有锁.
  3. sleep在睡眠同时,不会释放对象锁.但wait在等待时会释放对象锁.(失去Owner进入WaitSet中等待唤醒.)
  4. 它们的状态都是TIMED_WATING (相同之处)
synchronized(lock){while(条件不成立){lock.wait;}//干活
}//另一个线程
synchronized(lock){lock.notifyAll();//全唤醒
}

[Java并发]の其二相关推荐

  1. Java改知能机_Java 面试突击之 Java 并发知识基础 进阶考点全解析

    版权说明:本文内容根据 github 开源项目整理所得 项目地址:https://github.com/Snailclimb/JavaGuide​github.com 一.基础 什么是线程和进程? 何 ...

  2. 构建Java并发模型框架

    2002 年 2 月 22 日 Java的多线程特性为构建高性能的应用提供了极大的方便,但是也带来了不少的麻烦.线程间同步.数据一致性等烦琐的问题需要细心的考虑,一不小心就会出现一些微妙的,难以调试的 ...

  3. Java并发面试,幸亏有点道行,不然又被忽悠了

    2019独角兽企业重金招聘Python工程师标准>>> 前言 面试Java,必然要被问Java内存模型和Java并发开发.我被问到的时候,心里慌得一批,"额,是在<T ...

  4. Java并发 -- JMM

    文章基于jdk1.7,通过学习<Java并发编程的艺术>,对Java内存模型的理解 并发编程模型的两个关键问题 线程之间如何通信 线程之间如何同步 上面所说的线程指的是并发执行的活动实体. ...

  5. java 并发统计_java并发编程|CountDownLatch计数器

    0x01,CountDownLatch介绍 CountDownLatch是一个计数器,作为java并发编程中三个组件之一,这个组件的使用频率还是很多的.这里分享下自己画的java并发编程组件的图,后面 ...

  6. Java并发编程71道面试题及答案

    Java并发编程71道面试题及答案 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方 ...

  7. Java并发编程有多难?这几个核心技术你掌握了吗?

    本文主要内容索引 1.Java线程 2.线程模型 3.Java线程池 4.Future(各种Future) 5.Fork/Join框架 6.volatile 7.CAS(原子操作) 8.AQS(并发同 ...

  8. Java并发编程73道面试题及答案——稳了

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户 ...

  9. Java并发编程:JMM和volatile关键字

    Java内存模型 随着计算机的CPU的飞速发展,CPU的运算能力已经远远超出了从主内存(运行内存)中读取的数据的能力,为了解决这个问题,CPU厂商设计出了CPU内置高速缓存区.高速缓存区的加入使得CP ...

  10. Java并发编程:线程封闭和ThreadLocal详解

    什么是线程封闭 当访问共享变量时,往往需要加锁来保证数据同步.一种避免使用同步的方式就是不共享数据.如果仅在单线程中访问数据,就不需要同步了.这种技术称为线程封闭.在Java语言中,提供了一些类库和机 ...

最新文章

  1. c#获取DataTable某一列不重复的值,或者获取某一列的所有值
  2. Spring Cloud Zuul中使用Swagger汇总API接口文档
  3. 《快乐编程大本营》java语言训练班-第4课:java流程控制
  4. ASP.NET MVC 开源项目 收集
  5. Java并发编程的基础-线程的终止
  6. MarkDown之typora
  7. 1、vue 笔记之 组件
  8. horizon client 无法识别域_iText for Mac(OCR识别图中文字工具)
  9. 计算机机房用发电机组,应急康明斯计算机机房用发电机组怎么选有窍门
  10. java pv uv_使用Spark计算PV、UV
  11. android面试题2022
  12. 轻量android模拟器,夜神安卓模拟器6.2.0.0版:开启专业“特需”服务
  13. 由于改 UOM conversion 导致库存数量和财务上的数据错误
  14. android模拟器 diy,DIY泡沫黏液模拟器
  15. AEIA2018 | 中国工程院院士李骏:智能网联汽车的安全和自主创新是当下的关键...
  16. 2019XUPT_ACM 寒假训练第二期
  17. Linux C语言调用C++动态链接库-改
  18. 计算自然数e以及怎样理解为什么出现这么一个数
  19. jdk 11及以上 javax.annotation.Generated报错 @Generated报错
  20. (私人收藏)2019WER积木教育机器人赛(普及赛)解决方案-(全套)获取能源核心

热门文章

  1. 企业内部即时通讯系统项目总结
  2. 关于营销自动化,30个惊人的事实
  3. 同位语从句 vs 定语从句 区别
  4. Android轻松实现高效的启动页
  5. STM32单片机扩展下的IPUS SQPI PSRAM应用领域
  6. 直播实录|百度大脑EasyDL·NVIDIA专场 部署专家
  7. Java实现等额本息
  8. 【报告分享】2021中国医生洞察报告-丁香园(附下载)
  9. iOS调用第三方导航和线路规划
  10. 淘集集官宣破产,创业不易,且行且珍惜