具体的记录 synchronized 关键的各种使用方式, 注意事项. 感觉一步一步跟我来都可以看懂滴
大致是按照以下思路进行书写的. 黑体字可以理解为结论,
1.synchronized 锁的是什么?
2.synchronized 能够锁住所有方法吗?
3.synchronized 能够用来锁住一个方法之中的部分代码吗?
4.synchronized 能够锁住除了 this 以外的其他对象吗? 有什么用? 有什么需要注意的?
------------------------------------------------------------------------------------------ 正文 ------------------------------------------------------------------------------------------
1.synchronized 锁的是什么?
首先, 要明白非线程安全存在于实例变量之中, 即大家都可以更改的变量, 私有变量不存在线程安全问题. 那么解决非线程安全问题, 我们需要用用到 synchronized 来给某一个方法或者对象上锁, 避免交叉访问的现象出现. 那么 synchronized 到底锁的是什么呢?
先说结论, 锁的是一个对象, 一个类的实例, 而不是将一个方法锁起来, 如果想要在加上 synchronized 关键字之后同步运行, 那多个线程访问的必须是同一个对象, 这是锁的前提. 也可以理解为加上 synchronized 关键字之后同步访问的前提是多个线程访问的是同一个资源, 相当于他们是资源共享的.
用一个例子来说明:
twoNum.java 是我们的测试类, 里面有带锁的 addNum 方法, 根据目前的线程名字来赋予 num 不同的值, a 线程为 100,b 线程为 200
MyThread.java: 是自定义线程类, 用于, run 方法运行 twoNum 对象的 addNum() 方法
test.java:main 函数
twoNum.java:
package 第二章;
- public class twoNum {
- private int num=0;
- synchronized public void addNum(){
- try{
- if(Thread.currentThread().getName().equals("a")){
- num=100;
- System.out.println("a 线程设置 num 的值");
- Thread.sleep(2000);
- }else{
- num=200;
- System.out.println("b 线程设置 num 的值");
- }
- System.out.println(Thread.currentThread().getName()+" "+num);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- MyThread.java,
package 第二章;
import 第二章. twoNum;
- public class MyThread extends Thread {
- private twoNum twonum;
- public MyThread(twoNum temp){
- super();
- this.twonum=temp;
- }
- public void run(){
- super.run();
- twonum.addNum();
- }
- }
- test.java:
package 第二章;
- public class test {
- public static void main(String[] args){
- twoNum twonum = new twoNum();
- MyThread threadA = new MyThread(twonum);
- threadA.setName("a");
- MyThread threadB = new MyThread(twonum);
- threadB.setName("b");
- threadA.start();
- threadB.start();
- }
- }
运行上述代码, 不出意料的, 是线程安全的, 并同步运行, 因为我们给 twonum 对象的 addNum 方法上了锁, 并且线程 A,B 是用一个 twoNum 初始化的,
更改 test.java 代码如下, 用两个 twoNum 对象实例分别给 A,B 线程来初始化:
package 第二章;
- public class test {
- public static void main(String[] args){
- twoNum twonum1 = new twoNum();
- twoNum twonum2 = new twoNum();
- MyThread threadA = new MyThread(twonum1);
- threadA.setName("a");
- MyThread threadB = new MyThread(twonum2);
- threadB.setName("b");
- threadA.start();
- threadB.start();
- }
- }
运行结果如下:
可以看到现在 A,B 两个线程执行顺序虽然是异步的, 但是数据仍然是正常的. 为什么呢? 很明显, 因为有两个 twoNum 对象, 所以有两个对象锁, A,B 线程持有不同的锁, 所以他们在访问时, 访问的是不同对象, 那当然能异步运行了, 同时也有两个 num 变量, 从属于不同的线程, A 线程并不能够更改 B 线程当中的 num 变量, 所以数据也是正常的.
上面的例子看得出, 锁 关键字锁的是对象,
2.synchronized 能够锁住所有方法吗?
那么 synchronized 锁的是整个对象里面的所有方法, 还是怎么样呢?
先说结论: synchronized 只能够锁住一个对象当中带锁的方法, 并不是全部方法. 可以理解为局部同步.
这就意味着假如 A 线程拿到了一个对象的锁, 正在访问该对象之中的一个同步方法, 这时候 B 线程也尝试拿到同一个对象锁, 如果 B 线程访问的是该对象当中不带锁的方法, 那么久能够拿到该锁并访问, 如果访问的是该对象之中带锁的方法, 那么 B 线程无法拿到该锁, 只能等 A 线程释放锁之后才能拿到锁.
下面是一个例子:
修改 twoNum.java 如下: 增加了一个没有锁的方法 addNum2
package 第二章;
- public class twoNum {
- private int num=0;
- synchronized public void addNum(){
- try{
- if(Thread.currentThread().getName().equals("a")){
- num=100;
- System.out.println("a 线程设置 num 的值");
- Thread.sleep(2000);
- }else{
- num=200;
- System.out.println("b 线程设置 num 的值");
- }
- System.out.println(Thread.currentThread().getName()+" "+num);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- public void addNum2(){
- try {
- System.out.println(Thread.currentThread().getName() + "正在访问");
- Thread.sleep(1000);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- System.out.println("访问结束");
- }
- }
修改 MyThread.java 文件如下: 两个线程, 一个运行有锁的方法, 另一个运行没有锁的
package 第二章;
import 第二章. twoNum;
- class MyThread1 extends Thread {
- private twoNum twonum;
- public MyThread1(twoNum temp){
- super();
- this.twonum=temp;
- }
- public void run(){
- super.run();
- twonum.addNum();
- }
- }
- class MyThread2 extends Thread {
- private twoNum twonum;
- public MyThread2(twoNum temp){
- super();
- this.twonum=temp;
- }
- public void run(){
- super.run();
- twonum.addNum2();
- }
- }
- test.java:
- public class test {
- public static void main(String[] args){
- twoNum twonum = new twoNum();
- MyThread1 threadA = new MyThread1(twonum);
- threadA.setName("a");
- MyThread2 threadB = new MyThread2(twonum);
- threadB.setName("b");
- threadA.start();
- threadB.start();
- }
- }
运行结果如图:
可以看到 A 对象拿到了锁, 但是 B 线程仍然在同时访问了没有锁的 addNum2() 方法, 证明了上述结论, 其他线程可以在对象已经被占用的情况下可以异步访问同一个对象的没有锁的方法, 但是有锁的方法却不行. 有锁的不行这里不演示了, 很简单.
理解了这一个概念, 就可以解决有时候会碰到的脏读现象,
脏读很好理解, 比如你现在执行一个 setValue() 函数, 该函数更改两个值, username 和 password, 当更改完 username 还没有更改 password 的时候, 调用了 getValue() 方法获取这两个变量的值, 那获取到的 username 是已经更改过的, 但是 password 是没有更改的, 这就出现了脏读.
这时候, 运用我们所掌握的知识, 给 setValue() 和 getValue() 方法都加上锁, 这样子在 setValue() 执行结束之前, getValue() 就无法拿到对象锁获取信息, 这就解决了脏读问题.
接下来记录几个结论, 比较容易理解就不展示例子了, 只做记录:
1. 出现异常, 锁自动释放
2, 同步方法不具有继承性, 即父类有一个同步方法, 那么他的子类如果有一个多态方法, 那么子类中的方法想要同步必须加上 synchronized 关键字, 不能继承;
3.synchronized 能够锁住一个方法之中的部分代码吗?
可以看到, 前面说的都是给整个方法上锁, 但是想一下, 如果这个方法的执行时间会很久, A 线程先拿到了锁, B 线程如果要执行有锁的方法只能等待它执行完再执行, 那么效率会很低, 比如下面的例子:
twoNum.java: 模拟一个任务
package 第二章;
- public class twoNum {
- private String data;
- synchronized public void addNum(){
- try{
- System.out.println("开始");
- Thread.sleep(3000);
- data = Thread.currentThread().getName();
- System.out.println("结束");
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
MyThread1. 类, 线程类: 记录执行时间
- class MyThread1 extends Thread {
- long time1;
- long time2;
- private twoNum twonum;
- public MyThread1(twoNum temp){
- super();
- this.twonum=temp;
- }
- public void run(){
- super.run();
- time1 = System.currentTimeMillis();
- twonum.addNum();
- time2=System.currentTimeMillis();
- }
- }
test.java: 主函数
package 第二章;
- public class test {
- public static void main(String[] args){
- twoNum twonum = new twoNum();
- MyThread1 threadA = new MyThread1(twonum);
- threadA.setName("a");
- MyThread1 threadB = new MyThread1(twonum);
- threadB.setName("b");
- threadA.start();
- threadB.start();
- try{
- Thread.sleep(6000);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- System.out.println("A 线程花费时间:"+(threadA.time2-threadA.time1));
- System.out.println("B 线程花费时间:"+(threadB.time2-threadB.time1));
- }
- }
运行结果如下:
可以看到 B 线程等待了 3 秒才执行, 相当于多花费了 3 秒的时间.
那么怎么解决呢? 使用 synchronized 同步代码块来解决
首先 synchronized 同步代码块就是说现在不给整个方法上锁, 只给方法之中的部分关键代码上锁, 这样当 A 线程拿到一个对象的锁时, B 线程仍然可以访问相同对象之中没有上锁的代码块, 但是不能访问上锁的代码块. 简单来说, 代码块锁 synchronized 锁的是一个对象的局部代码块, 其他线程仍然可以在没有锁的情况下访问非同步代码块.
改变上述 twoNum.java 的代码, 如下:
package 第二章;
- public class twoNum {
- private String data;
- public void addNum(){
- try{
- System.out.println("开始");
- Thread.sleep(3000);
- String temp = Thread.currentThread().getName();
- synchronized(this) {
- System.out.println("线程"+temp+"赋值当中");
- data=temp;
- System.out.println("线程"+temp+"赋值结束");
- }
- System.out.println("结束");
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
只锁住关键的 data 赋值代码, 其他代码块并不用锁, 运行如下:
可以看到, 开始结束时异步执行的, 但是赋值却是同步的. 证明了我们上面的结论, 只同步执行 synchronized 锁住的代码块.
4.synchronized 能够锁住除了 this 以外的其他对象吗? 有什么用? 有什么需要注意的?
你可能注意到了, 上面 synchronized(this) 里面锁住了 this, 这个意思就是说取到当前对象的锁, 那么这个 this 能不能换成其他的对象呢? 可以, 他可以是任何对象, 这个对象我们就叫做对象监听器. 那么不同的对象监听器 y 有什么区别呢?
首先对象监听器总体分为两类:
1.this, 即自身
2. 非 this 对象, 一般是实例变量或者方法的参数
那么第二种有什么用处呢? 假设这种情况, 现在有一个类, 里面有很多个 synchronized 方法, 执行起来确实是同步的, 但是会受到阻塞. 不过如果我们使用 synchronized(非 this 对象) 同步代码块来锁住一些代码块, 这些代码块和其他被锁住的方法就是异步的了, 因为他们锁的是不同对象, 这样就提升了效率.
简单一句话, synchronized(非 this 对象) 可以让锁住的代码块和其他方法异步执行, 下面用程序进行演示:
twoNum.java: 两个方法, 一个上锁, 另一个是 synchronized(非 this 对象) 同步代码块
package 第二章;
- public class twoNum {
- private String anything = new String();
- public void addNum(){
- try{
- synchronized(anything) {
- System.out.println("线程"+Thread.currentThread().getName()+"开始");
- Thread.sleep(3000);
- System.out.println("线程"+Thread.currentThread().getName()+"结束");
- }
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- synchronized public void addNum2(){
- try{
- System.out.println("线程"+Thread.currentThread().getName()+"开始");
- Thread.sleep(3000);
- System.out.println("线程"+Thread.currentThread().getName()+"结束");
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
MyThread.java: 定义了两个线程类, 一个运行 addNum() 另一个运行 addNum2()
package 第二章;
import 第二章. twoNum;
- class MyThread1 extends Thread {
- long time1;
- long time2;
- private twoNum twonum;
- public MyThread1(twoNum temp){
- super();
- this.twonum=temp;
- }
- public void run(){
- super.run();
- twonum.addNum();
- }
- }
- class MyThread2 extends Thread {
- private twoNum twonum;
- public MyThread2(twoNum temp){
- super();
- this.twonum=temp;
- }
- public void run(){
- super.run();
- twonum.addNum2();
- }
- }
- test.java:
- public class test {
- public static void main(String[] args){
- twoNum twonum = new twoNum();
- MyThread1 threadA = new MyThread1(twonum);
- threadA.setName("a");
- MyThread2 threadB = new MyThread2(twonum);
- threadB.setName("b");
- threadA.start();
- threadB.start();
- }
- }
运行结果如下:
可以看到他们是异步执行的, 就是因为他们锁的是不同的对象.
不过要注意两点
1.java 有字符串常量池, 也就是如果有两个 String 对象, 但是他们的值是相同的, 那么当他们作为对象监听器时, 他们是被看做同一个锁的.
2.synchronized 如果加到一个静态方法上, 那么它锁的就不是一个对象, 而是整个类了. 这时候可以理解为只锁了静态方法, 该类的实例对象的锁还是可以正常拿到的.
下面看看 java 多线程死锁:
简单理解就是多个线程都在互相等待对方释放锁然后执行, 双方互相持有对方的锁, 这一般是程序 bug, 这块简单理解一下就行.
来源: https://www.cnblogs.com/eenio/p/11348861.html