Java学习线程安全

线程同步(线程安全处理Synchronized)

线程同步的两种方式:

1、同步代码块

2、同步方法

同步代码块

同步代码块: 在代码块声明上 加上synchronized

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
package com.oracle.demo01;
 
/*格式:
 * synchronized(任意对象){
 *    线程要操作的共享数据
 * }
 *  任意对象:同步对象、同步锁、对象监视器
 *  同步怎么能保证安全性?
 *  没有锁的线程不能执行,只能等待
 
 *  线程获得CPU资源以后想要执行同步代码块的内容,它先去看一下同步锁有没有
 *  如果有,那么你的线程获得锁,然后进入同步代码块执行代码,虽然在同步代码块中,线程休眠了
 *  在此线程休眠时,其他线程获得资源想要执行同步代码块,它先去看一看有没有锁,一看,没有锁
 *  它就被阻挡在代码块之后,只有等
 *  休眠的线程睡完了,起来继续执行代码块中的内容,全部执行完代码块中的内容,就释放锁
 *  然后其他线程获得锁,就可以继续执行了
 
 *  线程安全了,那么执行速度就会变慢了
 * */
public class Ticket implements Runnable {
    private int ticket = 100;
    Object obj = new Object();
 
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出售第" + (ticket--) + "张票");
                }
            }
 
        }
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.oracle.demo01;
 
public class Test {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t0 = new Thread(t);
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t0.start();
        t1.start();
        t2.start();
    }
}

同步方法

同步方法:在方法声明上加上synchronized

public synchronized void method(){

    可能会产生线程安全问题的代码

}

同步方法中的锁对象是 this

静态同步方法: 在方法声明上加上static synchronized

public static synchronized void method(){

可能会产生线程安全问题的代码

}

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
package com.oracle.demo02;
 
/*
 *同步方法中有锁么?
 *有锁,同步方法中的对象锁,就是本类对象引用 this
 *StringBuffer:之所以安全,就是因为里面有同步方法,只要有同步,就安全,就慢
 *StringBuilder:不安全,就是因为没有同步,就快
 *
 *如果你的同步方法时静态的,还有锁么?还是this么?
 *有锁,不是this,是本类自己,也就是ticket.class   本类类名.class
 * */
public class Ticket implements Runnable {
    private int ticket = 100;
    Object obj = new Object();
 
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            //synchronized (this)
            synchronized (obj) {
               method();
            }
 
        }
    }
   //同步代码块,代码更简洁
    public synchronized void method() {
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "出售第" + (ticket--) + "张票");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.oracle.demo01;
 
public class Test {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t0 = new Thread(t);
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t0.start();
        t1.start();
        t2.start();
    }
}

Lock接口

Lock 实现提供了比使用 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
package com.oracle.demo03;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
 * JDK5以后出现Lock接口的可以代替同步关键字
 * 功能是一样的,但是更灵活
 * */
public class Ticket implements Runnable {
    private int ticket = 100;
    //接口不能实例化对象,就用子类的引用
    private Lock l=new ReentrantLock();
 
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            l.lock();  
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出售第" + (ticket--) + "张票");
                }else{
                    return;
                }
                l.unlock();
            }
 
        }
}
package com.oracle.demo03;
 
public class Test {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t0 = new Thread(t);
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t0.start();
        t1.start();
        t2.start();
    }
}

死锁

死锁的前提就是:一个线程必须同时拥有两个对象的资源才能执行程序。
1.线程1 首先占有对象1,接着试图占有对象2
2. 线程2 首先占有对象2,接着试图占有对象1
3. 线程1 等待线程2释放对象2
4. 与此同时,线程2等待线程1释放对象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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.oracle.demo04;
 
public class LockA {
    private LockA() {
 
    }
    //保证A锁的唯一性
    public final static LockA la = new LockA();
 
}
package com.oracle.demo04;
 
public class LockB {
    private LockB() {
 
    }
    //保证了B锁的唯一性
    public final static LockB lb = new LockB();
}
package com.oracle.demo04;
 
public class DeadLock implements Runnable {
    private int i = 0;
 
    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {
                synchronized (LockA.la) {
                    System.out.println("if...LockA");
                    synchronized (LockB.lb) {
                        System.out.println("if...LockB");
                    }
                }
            } else {
                synchronized (LockB.lb) {
                    System.out.println("else...LockB");
                    synchronized (LockA.la) {
                        System.out.println("else...LockA");
 
                    }
                }
            }
            i++;
        }
 
    }
 
}
package com.oracle.demo04;
 
public class Test {
    public static void main(String[] args) {
        DeadLock dl = new DeadLock();
        Thread t0 = new Thread(dl);
        Thread t1 = new Thread(dl);
        t0.start();
        t1.start();
    }
}

等待唤醒机制

线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

等待唤醒机制:通过一定的手段使各个线程能够有效的利用资源,该手段就是等待唤醒机制

等待唤醒机制所涉及到的方法:

wait():等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中

notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的

notifyAll():唤醒全部,可以将线程池中的所有wait()线程都唤醒

所谓唤醒:就是让线程池中的线程具备执行资格。必须注意:这些方法都是在同步中才有效。同时这些方法在使用时必须表明所属锁,这样才可以明确这些方法操作的是哪个锁上的线程。

这些方法都被定义在Object类中了,因为这些方法在使用时,必须表明所属锁,而锁又可以是任意的,能被任意对象调用的方法一定定义在Object类中。

例如:

输入线程向Resource中输入name ,sex , 输出线程从资源中输出,先要完成的任务是:

l 1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();

l 2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.oracle.demo05;
 
public class Resouse {
    public  String name;
    public  String sex;
    public boolean flag=false;
}
//flag标记:为true的时候,赋值完成
//          为false的时候,获取值完成
//输入类:true,等待,false,赋值,赋值完成后,把flag变为true   notify输出,自己wait()
//输出类:false,等待,true,取值,取值完成后,把flag变为false  notify输入,自己wait()
 
package com.oracle.demo05;
 
public class Input implements Runnable {
    private Resouse r;
 
    public Input(Resouse r) {
        this.r = r;
    }
 
    @Override
    public void run() {
        // 往资源对象中输入数据
        int i = 0;
        while (true) {
            //r表明锁的唯一性
            synchronized (r) {
                if (r.flag) {
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if (i % 2 == 0) {
                    r.name = "张三";
                    r.sex = "女";
                } else {
                    r.name = "lisi";
                    r.sex = "nan";
                }
                r.flag = true;
                r.notify();
            }
            i++;
        }
    }
 
}
 
package com.oracle.demo05;
 
public class Output implements Runnable {
    private Resouse r;
 
    public Output(Resouse r) {
        this.r = r;
    }
 
    @Override
    public void run() {
        // 从资源对象中输出数据
        while (true) {
            synchronized (r) {
                if (!r.flag) {
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println(r.name + "..." + r.sex);
                r.flag = false;
                r.notify();
            }
 
        }
    }
}
 
package com.oracle.demo05;
 
 
public class Test {
    public static void main(String[] args) {
        Resouse r = new Resouse();
        Input in = new Input(r);
        Output out = new Output(r);
        Thread tin = new Thread(in);
        Thread tout = new Thread(out);
        tin.start();
        tout.start();
    }
}

  

 

posted @   低调的小孩儿  阅读(224)  评论(0编辑  收藏  举报
编辑推荐:
· 理解Rust引用及其生命周期标识(下)
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
阅读排行:
· 2025成都.NET开发者Connect圆满结束
· Ollama本地部署大模型总结
· langchain0.3教程:从0到1打造一个智能聊天机器人
· 在 VS Code 中,一键安装 MCP Server!
· 用一种新的分类方法梳理设计模式的脉络
点击右上角即可分享
微信分享提示