本文共 9516 字,大约阅读时间需要 31 分钟。
synchronized 关键字可以保证线程的原子性和可见性,它锁的是一个对象,他有如下几种用法
这里锁的是 object 对象的实例。
public class Demo1 { private int count = 10; private Object object = new Object(); public void test(){ synchronized (object) { while (count>0) { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } } } public static void main(String[] args){ Demo1 demo1 = new Demo1(); new Thread(()->demo1.test(),"t1").start(); new Thread(()->demo1.test(),"t2").start(); }}
这里使用的是 this 锁,锁的是当前类的实例。
public class Demo2 { private int count = 10; public void test(){ synchronized (this) { while (count > 0) { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } } } public static void main(String[] args){ Demo2 demo2 = new Demo2(); new Thread(()->demo2.test(),"t1").start(); new Thread(()->demo2.test(),"t2").start(); }}
直接声明在方法上,相当于相当于是 synchronized(this)
public class Demo3 { private int count = 10; public synchronized void test(){ while (count>0) { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } } public static void main(String[] args){ Demo3 demo3 = new Demo3(); new Thread(()->demo3.test(),"t1").start(); new Thread(()->demo3.test(),"t2").start(); }}
1)synchronized 关键字修饰的静态方法锁的是类的.class 文件;
2)静态方法中,锁定的对象只能是类的.class文件,不能是类的实例;public class Demo4 { private static int count = 10; //静态方法中synchronize锁定代码块,锁定的对象不能是类的实例,只能是类的.class文件。 public synchronized static void test(){ while (count>0) { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); }} public static void test2(){ //这里不能替换成this synchronized (Demo4.class){ while (count>0) { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } } } public static void main(String[] args){ Demo4 demo4 = new Demo4(); new Thread(()->demo4.test2(),"t1").start(); new Thread(()->demo4.test2(),"t2").start(); }}
当我们定义了一个对象锁,锁住了这个对象 o,如果这个对象的属性发生改变,不会影响锁住的对象,如果这个对象变成了一个新的对象,也就是重新 new 了一次,那锁住的对象就会发生改变,如下例子。
public class Demo1 { Object o = new Object(); public void test(){ synchronized (o) { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) { Demo1 demo = new Demo1(); new Thread(demo :: test, "t1").start(); Thread t2 = new Thread(demo :: test, "t2"); //去掉此句 t2 无法执行,否则 t2 能够执行 demo.o = new Object(); t2.start(); }}
如果使用字符串常量作为锁,在字符串相同的情况下,锁住的则是同一个对象,因为他们的引用都指向常量池的同一个地址。
public class Demo2 { String s1 = "hello"; String s2 = "hello"; public void test1(){ synchronized (s1) { System.out.println("t1 start..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 end..."); } } public void test2(){ synchronized (s2) { System.out.println("t2 start..."); } } public static void main(String[] args) { Demo2 demo = new Demo2(); new Thread(demo :: test1,"test1").start(); new Thread(demo :: test2,"test2").start(); }}
在 test1 中,是给整个方法上了锁,但由于我们的业务逻辑只需要锁住的对象是count++,所以在 test2 中我们只给了这个代码上了锁,这样细化了锁的粒度,使代码的效率得以提高。
public class Demo3 { int count = 0; public synchronized void test1(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } count ++; try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } public void test2(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (this) { count ++; } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }}
public class Demo{ public synchronized void test1(){ System.out.println(Thread.currentThread().getName() + " test1 start..."); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " test1 end..."); } public void test2(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " test2"); } public static void main(String[] args) { Demo demo = new Demo(); new Thread(demo :: test1,"test1").start(); new Thread(demo :: test2,"test2").start(); }}
synchronized支持重入,一个同步方法调用另一个同步方法,也可以得到锁。
public class Demo { synchronized void test1(){ System.out.println("test1 start........."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } test2(); } synchronized void test2(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test2 start......."); } public static void main(String[] args) { Demo demo= new Demo(); demo.test1(); }}
如果碰到异常情况,当先线程会自动释放锁
public class Demo { int count = 0; synchronized void test(){ System.out.println(Thread.currentThread().getName() + " start......"); while (true) { count ++; System.out.println(Thread.currentThread().getName() + " count = " + count); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 5) { //碰到异常的情况,如果没有处理,会自动释放锁,所以T2可以执行。 int i = 1/0; } } } public static void main(String[] args) { Demo demo11 = new Demo(); Runnable r = () -> demo11.test(); new Thread(r, "t1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(r, "t2").start(); }}
什么是可见性,在JMM(Java 内存模型) 中分为主存和本地私有内存,主存中存放的是所有线程的共享数据,本地内存中存放的是某个线程的私有数据,在某一线程启动时,会先去读取主内存的数据到本地私有内存,然后再运行这个副本,但并不会每次都去读取,所以会造成数据不一致问题。
volatile 关键字正是为了解决这个问题,我们通过以下代码举例。
以下代码,在不添加 volatile 关键字前,虽然 running 改变了,但线程 t1 仍然不会停止,是因为虽然主内存改变了,但本地私有内存并不知道。 当添加了 volatile 关键字后,每一次被 volatile 关键字修饰的变量的值发生改变时,主内存都会通知本地私有内存进行修改,所以线程 t1 可以结束。public class Demo { /*volatile*/ boolean running = true; public void test(){ System.out.println("test start..."); while (running){ } System.out.println("test end..."); } public static void main(String[] args) { Demo demo = new Demo(); new Thread(demo :: test,"t1").start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } demo.running = false; }}
如下代码,多个线程对一个共享变量进行累加,比如一个线程 A 加到了100,但是还没有往上添加,另一个线程 B 也来了,它也把 100 拿过来进行累加,他们两都加了 1 ,于是把结果101 存了进去,虽然是加了 2 次,但实际上只加了 1 次,所以 volatile关键字不保证原子性,它不能代替Synchronized。
public class Demo { volatile int count = 0; public void test(){ for (int i = 0; i < 10000; i++) { count ++; } } public static void main(String[] args) { Demo demo = new Demo(); Listthreads = new ArrayList(); for (int i = 0; i < 10; i++) { threads.add(new Thread(demo::test, "thread-" + i)); } threads.forEach((o)->o.start()); threads.forEach((o)->{ try { o.join(); } catch (Exception e) { e.printStackTrace(); } }); System.out.println(demo.count); }}
当我们在方法上添加了 synchronized ,发现最终结果为 10000,说明 synchronized 既保证了可见性,又保证了原子性。
public class Demo { int count = 0; public synchronized void test(){ for (int i = 0; i < 10000; i++) { count ++; } } public static void main(String[] args) { Demo demo = new Demo(); Listthreads = new ArrayList (); for (int i = 0; i < 10; i++) { threads.add(new Thread(demo::test, "thread-" + i)); } threads.forEach((o)->o.start()); threads.forEach((o)->{ try { o.join(); } catch (Exception e) { e.printStackTrace(); } }); System.out.println(demo.count); }}
公众号:【星尘Pro】
github:
推荐阅读
转载地址:http://ldfsi.baihongyu.com/