JAVA多线程安全

Posted 卡尼慕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA多线程安全相关的知识,希望对你有一定的参考价值。

使用多线程同时操作一个数据容易产生安全隐患,例如多窗口售票问题,票数固定,而同时多个窗口一起售票,容易导致最后数据出问题。


解决安全隐患:当一个线程进入数据操作的时候,无论是否休眠,其他线程只能等待。


JAVA程序提供技术称为同步技术。

公式:synchronized(任意的对象){

            线程要操作的共享数据

}

这给写法称为同步代码块!


同步对象:任意对象,同步锁,对象监视器

同步保证安全性:没有锁的线程不能执行,只能等待。


同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。


在同步中的线程不出同步,不会释放锁。

没有锁的线程,不能进入同步。


另外一种方式是同步方法:

public synchronized void method(){

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

}


同步方法也有锁,同步方法中的对象锁,是本类对象引用this。


静态方法中,同步也有锁,这里的锁不是this,锁是本类自己(类名.class)。

public static synchronized void method(){

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

}


死锁

当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。


这里就尝试让A,B锁相互嵌套,产生死锁。

1public class LockA {
2    private LockA(){}
3
4    public  static final LockA locka = new LockA();
5}


1public class LockB {
2    private LockB(){}
3
4    public static final LockB lockb = new LockB();
5}


1public class DeadLockDemo {
2    public static void main(String[] args) {
3        DeadLock dead = new DeadLock();
4        Thread t0 = new Thread(dead);
5        Thread t1 = new Thread(dead);
6        t0.start();
7        t1.start();
8    }
9}
 1public class DeadLock implements Runnable{
2    private int i = 0;
3    public void run(){
4        while(true){
5            if(i%2==0){
6                //先进入A同步,再进入B同步
7                synchronized(LockA.locka){
8                    System.out.println("if...locka");
9                    synchronized(LockB.lockb){
10                        System.out.println("if...lockb");
11                    }
12                }
13            }else{
14                //先进入B同步,再进入A同步
15                synchronized(LockB.lockb){
16                    System.out.println("else...lockb");
17                    synchronized(LockA.locka){
18                        System.out.println("else...locka");
19                    }
20                }
21            }
22            i++;
23        }
24    }
25}


Lock接口

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。就是把可能会产生线程安全问题的代码上锁的过程拆分开了,前面获取锁(调用方法lock()),执行完后释放锁(unlock())。

1Lock lock = new ReentrantLock();
2lock.lock();
3try {
4     。。。
5} finally {
6     lock.unlock();
7}


 1public interface Lock {
2    //获取锁,调用该方法将会获取锁,当锁获取后,从该方法返回
3    void lock();
4    //可中断地获取锁,和lock()方法的不同之处在于该方法会响应中断,即在锁的获取过程中可以中断当前线程 
5    void lockInterruptibly() throws InterruptedException;
6    //尝试非阻塞的获取锁,调用该方法后会立刻返回,如果能够获取则返回true,否则返回false 
7    boolean tryLock();
8    //超时地获取锁 1、当前线程在超时时间内成功获取锁。2、当前线程在超时时间内被中断。3、超时时间结束返回false。 
9    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
10    //释放锁 
11    void unlock();
12    //获取等待通知组件
13    Condition newCondition();
14}


等待唤醒机制

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


通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制


常见的涉及到的方法:

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

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

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


这里的唤醒实际上是让线程池中的线程具有执行代码的资格。并且这些代码都是在同步中才有效,这些方法在使用时必须表明所属锁(也就是用锁对象调用)。也因此,这些方法并不是定义在Thread类中,而是定义在Object中,因为所属锁可以是任意对象。

 1/*
2 *  定义资源类,有2个成员变量
3 *  name,sex
4 *  同时有2个线程,对资源中的变量操作
5 *  1个对name,age赋值
6 *  2个对name,age做变量的输出打印
7 */

8public class Resource {
9    public String name;
10    public String sex;
11    public boolean flag = false;
12}
13
14/*
15 *  输入的线程,对资源对象Resource中成员变量赋值
16 *  一次赋值 张三,男
17 *  下一次赋值 lisi,nv
18 */

19public class Input implements Runnable {
20    private Resource r ;
21
22    public Input(Resource r){
23        this.r = r;
24    }
25
26    public void run() {
27        int i = 0 ;
28        while(true){
29          synchronized(r){
30              //标记是true,等待
31                if(r.flag){
32                    try{r.wait();}catch(Exception ex){}
33                }
34
35                if(i%2==0){
36                    r.name = "张三";
37                    r.sex = "男";
38                }else{
39                    r.name = "lisi";
40                    r.sex = "nv";
41                }
42                //将对方线程唤醒,标记改为true
43                r.flag = true;
44                r.notify();
45          }
46            i++;
47        }
48    }
49
50}
51
52/*
53 *  输出线程,对资源对象Resource中成员变量,输出值
54 */

55public class Output implements Runnable {
56    private Resource r ;
57
58    public Output(Resource r){
59        this.r = r;
60    }
61    public void run() {
62        while(true){
63          synchronized(r){  
64              //判断标记,是false,等待
65            if(!r.flag){
66                try{r.wait();}catch(Exception ex){}
67            }
68            System.out.println(r.name+".."+r.sex);
69            //标记改成false,唤醒对方线程
70            r.flag = false;
71            r.notify();
72          }
73        }
74    }
75}
76
77
78public class ThreadDemo{
79    public static void main(String[] args) {
80
81        Resource r = new Resource();
82
83        Input in = new Input(r);
84        Output out = new Output(r);
85
86        Thread tin = new Thread(in);
87        Thread tout = new Thread(out);
88
89        tin.start();
90        tout.start();
91    }
92}

总结

1、线程同步的方法:同步代码块、同步方法。同步代码块的锁对象可以是任意的对象,同步方法中的锁对象是 this,静态同步方法中的锁对象是 类名.class。

2、多线程的实现:继承Thread类、实现Runnable接口、通过线程池实现Callable接口。

3、run()与start()

start: 启动线程,并调用线程中的run()方法

run  : 执行该线程对象要执行的任务

4、sleep()与wait()

sleep: 不释放锁对象, 释放CPU使用权,在休眠的时间内,不能唤醒。

wait(): 释放锁对象, 释放CPU使用权,在等待的时间内,能唤醒。

5、wait()、notify()必须是锁对象调用!!!

以上是关于JAVA多线程安全的主要内容,如果未能解决你的问题,请参考以下文章

多线程--线程安全

多线程--线程安全

温故Java基础多线程编程—线程安全

Java并发编程:多线程环境中安全使用集合API(含代码)

java中多线程安全性和同步的常用方法

怎样去写线程安全的代码(Java)