随笔 - 51  文章 - 0  评论 - 9  阅读 - 27万

java线程同步以及对象锁和类锁解析(多线程synchronized关键字)

一、关于线程安全

1.是什么决定的线程安全问题?
  线程安全问题基本是由全局变量静态变量引起的。
  若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

2.可以解决多线程并发访问资源的方法有哪些?
  主要有三种方式:分别是同步代码块 、同步方法和锁机制(Lock)
  其中同步代码块和同步方法是通过关键字synchronized实现线程同步

本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁和类锁

二、synchronized关键字各种用法与实例

  事实上,synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁。

synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。

因此,事实上synchronized关键字可以细分为上面描述的五种用法。
1.同步块synchronized (this)

复制代码
public class Ticket1 extends Thread{
    
    private int nums = 0; //出票数
    private int count =20; //剩余

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if(count <= 0) {
                    break;
                }
                nums++;
                count--;
                try {
                    Thread.sleep(550);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("显示出票信息:"+Thread.currentThread().getName()+
                "抢到第"+nums+"张票,剩余"+count+"张");
            }
        }
    }
    

    public static void main(String[] args) {
        Ticket1 ticket1 = new Ticket1();
        Thread anni = new Thread(ticket1,"安妮");
        Thread jack = new Thread(ticket1,"jack");
        anni.start();
        jack.start();

    }

}
复制代码

这样是同步的,线程获取的是同步块synchronized (this)括号()里面的对象实例的对象锁,这里就是Ticket1 实例对象的对象锁了。

需要注意的是synchronized (){}的{}前后的代码依旧是异步的

2.synchronized (非this对象)的用法锁的是对象

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Ticket2 implements Runnable{
 
    private int nums = 0; //出票数
    private int count =25; //剩余
    //实现线程安全三种方法
    private final Object lock = new Object();//1.使用私有不变对象锁,使得攻击者无法获取到锁对象(推荐使用)
     
    @Override
    public void run() {
        while (true) {
            //2.this 使用对象自身的锁(隐式锁)--对象锁
            //3.Ticket2.class 给Ticket2加锁 --类锁->使用说明:静态方法则一定会同步,非静态方
            //法需在单例模式才生效(本例为单例),但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。
            synchronized (lock) {
                if(count <= 0) {
                    break;
                }
                nums++;
                count--;
                try {
                    Thread.sleep(750);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("显示出票信息:"+Thread.currentThread().getName()+
                "抢到第"+nums+"张票,剩余"+count+"张");
            }
        }
    }
     
    public static void main(String[] args) {
        Ticket2 ticket2 = new Ticket2();
        Thread zhangsan = new Thread(ticket2,"张三");
        Thread zhaoyun = new Thread(ticket2,"赵云");
        zhangsan.start();
        zhaoyun.start();
    }
 
}

例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Run2 {
 
    public static void main(String[] args) {
 
        Service service = new Service("xiaobaoge");
 
        ThreadA2 a = new ThreadA2(service);
        a.setName("A");
        a.start();
 
        ThreadB2 b = new ThreadB2(service);
        b.setName("B");
        b.start();
 
    }
 
}
 
class Service {
 
    String anyString = new String();
 
    public Service(String anyString){
        this.anyString = anyString;
    }
 
    public void setUsernamePassword(String username, String password) {
        try {
            synchronized (anyString) {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "进入同步块");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "离开同步块");
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
}
 
class ThreadA2 extends Thread {
    private Service service;
 
    public ThreadA2(Service service) {
        super();
        this.service = service;
    }
 
    @Override
    public void run() {
        service.setUsernamePassword("a", "aa");
 
    }
 
}
 
 
class ThreadB2 extends Thread {
 
    private Service service;
 
    public ThreadB2(Service service) {
        super();
        this.service = service;
    }
 
    @Override
    public void run() {
        service.setUsernamePassword("b", "bb");
 
    }
 
}

3.synchronized (class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Run {
 
    public static void main(String[] args) {
 
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();
 
        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
 
    }
 
}
class Service {
 
    public static void printA() {
        synchronized (Service.class) {
            try {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "进入printA");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "离开printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
 
    }
 
    public static void printB() {
        synchronized (Service.class) {
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "进入printB");
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "离开printB");
        }
    }
}

4.静态synchronized同步方法  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Run {
 
    public static void main(String[] args) {
 
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();
 
        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
 
    }
 
}
 
class Service {
 
    synchronized public static void printA() {
        try {
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    synchronized public static void printB() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
                + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
                + System.currentTimeMillis() + "离开printB");
    }
 
}
 
 
class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }
 
}
 
 
class ThreadB extends Thread {
    @Override
    public void run() {
        Service.printB();
    }
}

5.synchronized修饰非静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class Run {
 
    public static void main(String[] args) {
 
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
 
        ThreadA athread = new ThreadA(numRef);
        athread.start();
 
        ThreadB bthread = new ThreadB(numRef);
        bthread.start();
 
    }
 
}
class HasSelfPrivateNum {
 
    private int num = 0;
 
    synchronized public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
class ThreadA extends Thread {
 
    private HasSelfPrivateNum numRef;
 
    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
 
    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
 
}
class ThreadB extends Thread {
 
    private HasSelfPrivateNum numRef;
 
    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
 
    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
 
}

实验结论:两个线程访问同一个对象中的同步方法是一定是线程安全的。本实现由于是同步访问,所以先打印出a,然后打印出b

这里线程获取的是HasSelfPrivateNum的对象实例的锁——对象锁。

 

posted on   鲁班快跑  阅读(863)  评论(1编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示