并发-3-synchronized与ReentrantLock

线程安全问题:

在多线程中,有可能出现多个线程同时使用同一个资源的情况,这个资源可以是变量,数据表,txt文件等。这个资源称作"临界资源"

举个例子:取钱这个线程分为三个步骤:1.读取金额、2.取款、3.更新金额

有个典型的线程安全的例子,倘若A,B两人使用同一个账户(1000元)取款,执行顺序如下:

1.A读取金额,显示1000

2.A取款300元

3.B读取金额,显示为1000(实际应该为700)

4.B取款1000元

5.A更新金额为700

6.B更新金额0

之所以账户中莫名地被多取了300,是因为A和B两个线程使用了同一个临界资源(账户)

注意:当多个线程执行同一个方法时,方法内部的变量不是临界资源,因为每个线程都有自己独立的内存区域(PC,方法栈,线程栈)   
复制代码

解决线程安全问题:

基本所有的并发方案,都采用“序列化访问资源”,也就是在同一时间,只有一个线程能访问临界资源,也称作同步互斥访问

方案1:synchronized

在Java中,每个对象都有一个锁标记,称为monitor(监视器),多个线程访问这个对象的临界资源时,只有获取了该对象的锁才能访问

在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
复制代码
public class ThreadSynchronized2 {

    public static void main(String[] agrs) {
        InsertData insertData = new InsertData();

        new Thread("线程1") {
            @Override
            public void run() {
                insertData.insert(Thread.currentThread());
            }
        }.start();

        new Thread("线程2") {
            @Override
            public void run() {
                insertData.insert(Thread.currentThread());
            }
        }.start();
    }
}

class InsertData extends Thread {
    public synchronized void insert(Thread thread) {
        for (int i = 0; i < 5; i++) {
            System.out.println(thread.getName() + "插入: " + i);
        }
    }
}
复制代码

输出:

线程1插入: 0
线程1插入: 1
线程1插入: 2
线程1插入: 3
线程1插入: 4
线程2插入: 0
线程2插入: 1
线程2插入: 2
线程2插入: 3
线程2插入: 4
复制代码

还可以改成以下两种方式:

class InsertData extends Thread {
    public void insert(Thread thread) {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(thread.getName() + "插入: " + i);
            }
        }
    }
}
复制代码
class InsertData extends Thread {
    private Object object = new Object();
    public void insert(Thread thread) {
        synchronized (object) {
            for (int i = 0; i < 5; i++) {
                System.out.println(thread.getName() + "插入: " + i);
            }
        }
    }
}
复制代码

说明:

    1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

    2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,

    3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型的对象,也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。
复制代码

注意事项:

static方法,使用的是类锁(多个对象使用同一个类的monitor)

和非static方法,使用的是对象锁(每个对象维护一个monitor)
复制代码

synchronized的优缺点:

1.使用synchronized包住的代码块,只可能有两种状态:顺利执行完毕释放锁、执行时发生异常释放锁,不会由于异常导致出现死锁现象

2.如果synchronized包住的代码块中有sleep等操作,比如I/O阻塞,但是其他线程还是需要等待,这样程序的效率就比较低了

3.等待synchronized释放锁的线程会一直等待下去(死心塌地,不到黄河心不死)
复制代码

举例场景:

对a.txt这个文件,A,B线程都可以进行读写,写操作与写操作会发生冲突,写操作与读操作会发生冲突,但是,读操作与读操作不会发生冲突

这个时候synchronized满足不了需求,就需要用高级的锁
复制代码

锁的介绍:

先看JDK中对于Lock的定义:

public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();

}
复制代码
通过Lock的源码可以知道,lock(),tryLock(),tryLock(long time,Time Unit),lockInterruptiably()是用来获取锁的

lock()方法是平常使用得最多的一个方法,用来获取锁,如果锁已经被其他线程获取,则进行等待,可以说和synchronized没有差别
复制代码
Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){
}finally{
    lock.unlock();   //释放锁
}
复制代码

实际例子:

public class ThreadLock {

    private ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        ThreadLock mt = new ThreadLock();
        new Thread(() -> mt.insert()).start();
        new Thread(() -> mt.insert()).start();
    }

    public void insert() {
        lock.lock();
        try {
            //睡眠10s,等待锁的线程此时挂起
            System.out.println(Thread.currentThread().getName() + "获得锁");
            Thread.currentThread().sleep(10000);
        } catch (Exception e) {
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放锁");
            lock.unlock();
        }
    }
}
复制代码

输出:

Thread-0获得锁
Thread-0释放锁
Thread-1获得锁
Thread-1释放锁
复制代码

tryLock()是有返回值的,且会立即返回,不会一直等待,如果获取到锁,则返回true,否则返回false

public class ThreadLock2 {

    private ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        ThreadLock2 mt = new ThreadLock2();
        new Thread(() -> mt.insert(Thread.currentThread())).start();
        new Thread(() -> mt.insert(Thread.currentThread())).start();
    }

    public void insert(Thread thread) {
        if (lock.tryLock()) {
            try {
                System.out.println(thread.getName() + "得到了锁");
            } catch (Exception e) {
            } finally {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + "释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName() + "获取锁失败");
        }
    }
}
复制代码

输出:

Thread-0得到了锁
Thread-1获取锁失败
Thread-0释放了锁
复制代码

lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。

lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。

示例代码:

public class ThreadLock3 {

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        MyThread thread1 = new MyThread("1", lock);
        MyThread thread2 = new MyThread("2", lock);
        thread1.start();
        Thread.sleep(100);
        thread2.start();
        Thread.sleep(1000);
        thread2.interrupt();
    }
}

class MyThread extends Thread {
    private Lock lock = null;

    public MyThread(String name, Lock lock) {
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
        try {
            lock.lockInterruptibly();
            out.println(this.getName() + "得到了锁");
            long startTime = System.currentTimeMillis();
            for (; ; ) {
                if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) {
                    break;
                }
            }
        } catch (InterruptedException e) {
            out.println(Thread.currentThread().getName() + "被中断");
        } finally {
            out.println(Thread.currentThread().getName() + "执行finally");
        }
    }
}
复制代码

输出:

1得到了锁
2被中断
2执行finally
复制代码

ReentranReadWriteLock分为readLock()和writeLock()来获取读写锁

public class ReadAndWriteLock {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        ReadAndWriteLock readAndWriteLock = new ReadAndWriteLock();
        new Thread(() -> readAndWriteLock.read()).start();
        new Thread(() -> readAndWriteLock.read()).start();
        new Thread(() -> readAndWriteLock.write()).start();
        new Thread(() -> readAndWriteLock.write()).start();
    }

    public void read() {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "正在进行读操作");
            long startTime = System.currentTimeMillis();
            while ((System.currentTimeMillis() - startTime) < 10000){}
        } catch (Exception e) {
        } finally {
            System.out.println(Thread.currentThread().getName() + "读操作完毕");
            lock.readLock().unlock();
        }
    }

    public void write() {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "正在进行写操作");
            long startTime = System.currentTimeMillis();
            while ((System.currentTimeMillis() - startTime) < 10000){}
        } catch (Exception e) {
        } finally {
            System.out.println(Thread.currentThread().getName() + "写操作完毕");
            lock.writeLock().unlock();
        }
    }
}
复制代码

输出:

Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0读操作完毕
Thread-1读操作完毕
Thread-2正在进行写操作
Thread-2写操作完毕
Thread-3正在进行写操作
Thread-3写操作完毕
复制代码

Lock和synchronized的选择:

1.Lock是JDK并发包的一个类,synchronized是关键字

2.Lock必须要使用unlock()方法在finally中释放,如果没有主动释放锁,就有可能导致出现死锁现象,发生异常时不会自动释放,synchronized发生异常时会自动释放

3.Lock在等待获取锁的时候可以响应中断,synchronized则不会。

4.Lock可以知道获取锁是否成功

5.Lock有读写锁功能

6.Lock可以在指定的时间范围内获取所,如果截止时间到了,依然没有获得锁,则返回复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值