小卜java

JAVA面试汇总(二)多线程(六)

2021-12-16  本文已影响0人  汤太咸啊

JAVA多线程内容比较多,今天写完了第六篇,后边还有七(肯定最后一篇了)。

  1. 如何保证多线程读写文件的安全?
    (1)读写互斥,写读互斥,写写互斥,只有读读相容(可以异步)
    (2)FileInputStream、FileOutputStream、RandomAccessFile均可得到FileChannel对象
    (3)FileChannel通过独占锁tryLock()锁定文件,用于写文件。
    (4)FileChannel的tryLock(0, Long.MAX_VALUE, true)是非阻塞的,是共享锁,能被多个线程同时持有,它能禁止其他线程获取独占锁(防止写进程进来写文件),可用于读文件。
    (5)FileChannel的lock()是阻塞的,在文件被锁定的情况下,会保持阻塞,直到获得该锁为止,实际上这个可以用作喝tryLock一样的情况,用于写文件。
//一般用这种方式,来循环获取锁,获取到锁之后开始写操作。
File file;
try {
    file = FileUtils.createFile(pathFile);
    } catch (IOException e) {
        e.printStackTrace();
        ioListener.onFail("文件创建失败,请检查路径是否合法以及读写权限");
        return;
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel fileChannel = fileOutputStream.getChannel();
//文件锁
FileLock fileLock = null;
while (true) {
     try {
         fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, true);//共享锁
         break;
     } catch (Exception e) {
         System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
     }
}
  1. Java中的ReadWriteLock是什么?
    (1)ReetrantReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。
    (2)读锁使用共享模式;写锁使用独占模式;读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockTest {
    //默认是非公平锁
//    static ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
    //手工设置为公平锁
    static ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock(true);
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
//            new Thread(new RunnableDowngradeLock()).start();
            new Thread(new RunnableUpgradeLock()).start();
        }
    }
    //ReentrantReadWriteLock支持锁降级,但是一定记得显示的把两个锁都释放
    static class RunnableUpgradeLock implements Runnable{
        @Override
        public void run() {
            try {
                //先获取写锁
                rtLock.writeLock().lock();
                System.out.println(Thread.currentThread().getName() + " get writeLock");
                //再获取读锁,这种明显是降级,锁降级
                rtLock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + " get read lock");
                System.out.println(Thread.currentThread().getName() + " isWriteLocked "+ rtLock.isWriteLocked());
                System.out.println(Thread.currentThread().getName() + " isFair "+ rtLock.isFair());
            }finally {
                rtLock.writeLock().unlock();
                rtLock.readLock().unlock();
            }
        }
    }
    //ReentrantReadWriteLock不支持锁升级,下面这个执行会死锁
    static class RunnableDowngradeLock implements Runnable{
        @Override
        public void run() {
            try {
                //先获取读锁
                rtLock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + " get read lock");
                //再获取写锁,这种明显是升级了,锁升级
                rtLock.writeLock().lock();
                System.out.println(Thread.currentThread().getName() + " get writeLock");
            }finally {
                rtLock.writeLock().unlock();
                rtLock.readLock().unlock();
            }
        }
    }
}
  1. lock原理
    (1)Lock接口就这么几个方法
//尝试获取锁,获取成功则返回,否则阻塞当前线程
void lock()
//尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常 
void lockInterruptibly()
//尝试获取锁,获取锁成功则返回true,否则返回false 
boolean tryLock();
//尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常 
boolean tryLock(long time, TimeUnit unit)
//释放锁
void unlock()
//返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量
Condition newCondition()

(2)一定有一个变量是表示(锁)状态的变量(假设0表示没有线程获取锁,1表示已有线程占有锁),该变量必须声明为voaltile类型(任何线程过来,都要直接从内存读取锁锁状态,不可能从缓存取);
(3)锁操作步骤:1>先获取锁状态,如果状态时0未锁定时候,修改锁状态为1,返回锁定成功;2>如果时1已锁定,进入等待队列,将自身阻塞,等待锁变为0未锁定的唤醒该线程,然后执行第一步;3>修改锁状态时,有可能失败,抢占式的,只有第一个修改的能成功,后边的线程修改都失败,同样进入等待队列,将自身阻塞,等待锁变为0时唤醒该线程,然后执行第一步
(4)解除锁操作步骤:1>修改锁状态,将1变为0,然后通知等待队列的第一个线程(如果是公平锁的话,第一个等待的),释放完成;2>被唤醒的锁,执行上面的所操作步骤。3>如果锁是已经被释放了,还来解除锁操作,这个时候直接抛异常失败。
(5)公平锁实现:锁操作时,判断队列是否存在等待的线程,1>如果有,就把自己加入队列,然后将自身阻塞;2>如果没有等待的线程,执行所操作步骤第一步。3>队列按照先入先出操作,所以唤醒的一定是先进入队列的线程
(6)非公平锁实现:实际上就是是否可以插入到队列前面,第一个被唤醒一定是队列头的等待线程,哪个线程抢占了队列头,谁就是第一个。
(7)Condition接口,实际上就是维护的等待队列,先进先出队列

  1. ReentrantLock的内部实现
    (1)其实上面Lock已经说的差不多了
    (2)内部有抽象类Sync继承了AbstractQueuedSynchronizer。Sync有两个子类,一个FairSync另一个NonfairSync(ReentrantLock默认就是非公平锁)。
    (3)这个比Lock多了acquire和tryAcquire的方法,还有tryRelease/getOwner/getHoldCount/isLocked等方法。
    (4)tryAcquire方法分为公平锁/非公平锁的,公平锁:如果锁状态为0且等待队列没有等待的线程,则通过cas方式设置锁状态为1,设置拥有锁的线程为自己;如果锁状态大于1,判断拥有锁的是不是自己,是自己则将状态改为加1,返回成功,如果拥有锁的不是自己,返回失败。非公平锁:如果锁状态为0,就通过cas方式抢占锁,抢占成功设置状态,设置拥有锁的线程为自己,如果锁状态不为0,在判断拥有锁的是不是自己,是则+1,返回成功,不是则直接返回抢占失败。
    (5)acquire方法实际上在AbstractQueuedSynchronizer(AQS)里,这个会调用(4)的tryAcquire方法,如果失败把自己加入到等待队列中,成功则直接返回成功。
    (6)tryRelease就是tryAcquire的反向,为锁状态减去准备release的个数,也就是释放锁的个数。但是这个要判断解锁的是否为当前拥有锁的线程,否则直接失败。

  2. 多线程有什么要注意的问题?
    (1)需要创建大量多线程的程序,建议使用线程池,避免占用大量资源
    (2)控制并发数量,过大可能导致服务器无法支撑,cpu就那么多,大量并发执行反而卡死了
    (3)通过一些锁来控制并发导致的共享问题
    (4)使用锁的过程中注意开发时候尽量别设计成循环等待的,容易死锁
    (5)另外ReentrantLock一类的锁,lock后,一定要记得unlock,否则就死锁了
    (6)有些线程任务执行完成一部分后,需要等待其他线程执行完成后再执行,这个时候可以通过sleep或者其他方式使其进入阻塞状态。
    (7)有没有优先级?

感谢各位的阅读,帮忙点赞,感谢各位。

上一篇 下一篇

猜你喜欢

热点阅读