Lock ,synchronized,线程池

版权声明: https://blog.csdn.net/thedarkclouds/article/details/85236861

synchronized 的缺陷

synchronized 是 java 中的一个关键字, 也就是说是 Java 语言内置的特性。
如果一个代码块被 synchronized 修饰了, 当一个线程获取了对应的锁, 并执行该代码块
时, 其他线程便只能一直等待, 等待获取锁的线程释放锁, 而这里获取锁的线程释放锁只会
有两种情况:
1) 获取锁的线程执行完了该代码块, 然后线程释放对锁的占有;
2) 线程执行发生异常, 此时 JVM 会让线程自动释放锁。
例子 1:
如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep 方法) 被阻塞了,
但是又没有释放锁, 其他线程便只能干巴巴地等待, 试想一下, 这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的
时间或者能够响应中断) , 通过 Lock 就可以办到。
例子 2:
当有多个线程读写文件时, 读操作和写操作会发生冲突现象, 写操作和写操作会发生冲
突现象, 但是读操作和读操作不会发生冲突现象。
但是采用 synchronized 关键字来实现同步的话, 就会导致一个问题:
如果多个线程都只是进行读操作, 当一个线程在进行读操作时, 其他线程只能等待无法
进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时, 线程之间不会发生冲突, 通
过 Lock 就可以办到。
另外, 通过 Lock 可以知道线程有没有成功获取到锁。 这个是 synchronized 无法办到的。
总的来说, 也就是说 Lock 提供了比 synchronized 更多的功能。

lock 和 synchronized 的区别


1) Lock 不是 Java 语言内置的, synchronized 是 Java 语言的关键字, 因此是内置特性。
Lock 是一个类, 通过这个类可以实现同步访问;
2) Lock 和 synchronized 有一点非常大的不同, 采用 synchronized 不需要用户去手动释
放锁, 当 synchronized 方法或者 synchronized 代码块执行完之后, 系统会自动让线程释放对
锁的占用; 而 Lock 则必须要用户去手动释放锁, 如果没有主动释放锁, 就有可能导致出现
死锁现象。

Lock


首先要说明的就是 Lock, 通过查看 Lock 的源码可知, Lock 是一个接口:  常用方法
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
}

四个获取锁方法的区别:

lock()方法是平常使用得最多的一个方法, 就是用来获取锁。 如果锁已被其他线程获取,
则进行等待。
由于在前面讲到如果采用 Lock, 必须主动去释放锁, 并且在发生异常时, 不会自动释
放锁。因此一般来说, 使用 Lock 必须在 try{}catch{}块中进行, 并且将释放锁的操作放在 finally
块中进行, 以保证锁一定被被释放, 防止死锁的发生。
tryLock()方法是有返回值的, 它表示用来尝试获取锁, 如果获取成功, 则返回 true, 如
果获取失败(即锁已被其他线程获取) , 则返回 false, 也就说这个方法无论如何都会立即
返回。 在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和 tryLock()方法是类似的, 只不过区别在于这个方
法在拿不到锁时会等待一定的时间, 在时间期限之内如果还拿不到锁, 就返回 false。 如果
如果一开始拿到锁或者在等待期间内拿到了锁, 则返回 true。
lockInterruptibly()方法比较特殊, 当通过这个方法去获取锁时, 如果线程正在等待获取
锁, 则这个线程能够响应中断, 即中断线程的等待状态。 也就使说, 当两个线程同时通过
lock.lockInterruptibly()想获取某个锁时, 假若此时线程 A 获取到了锁, 而线程 B 只有等待,
那么对线程 B 调用 threadB.interrupt()方法能够中断线程 B 的等待过程。
注意, 当一个线程获取了锁之后, 是不会被 interrupt()方法中断的。
因此当通过 lockInterruptibly()方法获取某个锁时, 如果不能获取到, 只有进行等待的情
况下, 是可以响应中断的。
而用 synchronized 修饰的话, 当一个线程处于等待某个锁的状态, 是无法被中断的, 只
有一直等待下去
 

ReadWriteLock
ReadWriteLock 也是一个接口, 在它里面只定义了两个方法:

public interface ReadWriteLock {
/**
* Returns the lock used for reading.
* *
@return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
* *
@return the lock used for writing.
*/
Lock writeLock();
}


一个用来获取读锁, 一个用来获取写锁。 也就是说将文件的读写操作分开, 分成 2 个锁
来分配给线程, 从而使得多个线程可以同时进行读操作。 下面的 ReentrantReadWriteLock 实
现了 ReadWriteLock 接口。
 ReentrantReadWriteLock
ReentrantReadWriteLock 里面提供了很多丰富的方法, 不过最主要的有两个方法:
readLock()和 writeLock()用来获取读锁和写锁。

注意:
不过要注意的是, 如果有一个线程已经占用了读锁, 则此时其他线程如果要申请写锁,
则申请写锁的线程会一直等待释放读锁。
如果有一个线程已经占用了写锁, 则此时其他线程如果申请写锁或者读锁, 则申请的线
程会一直等待释放写锁。

扫描二维码关注公众号,回复: 4749663 查看本文章

 Lock 和 synchronized 的选择


1) Lock 是一个接口, 而 synchronized 是 Java 中的关键字, synchronized 是内置的语言
实现;
2) synchronized 在发生异常时, 会自动释放线程占有的锁, 因此不会导致死锁现象发
生; 而 Lock 在发生异常时, 如果没有主动通过 unLock()去释放锁, 则很可能造成死锁现象,
因此使用 Lock 时需要在 finally 块中释放锁;
3) Lock 可以让等待锁的线程响应中断, 而 synchronized 却不行, 使用 synchronized 时,
等待的线程会一直等待下去, 不能够响应中断;
4) 通过 Lock 可以知道有没有成功获取锁, 而 synchronized 却无法办到。
5) Lock 可以提高多个线程进行读操作的效率。
在性能上来说, 如果竞争资源不激烈, 两者的性能是差不多的, 而当竞争资源非常激烈
时(即有大量线程同时竞争) , 此时 Lock 的性能要远远优于 synchronized。 所以说, 在具体
使用时要根据适当情况选择。

java 并发包介绍


JDK5.0 以后的版本都引入了高级并发特性, 大多数的特性在 java.util.concurrent 包中,
是专门用于多线程发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并
发应用程序。 主要包含原子量、 并发集合、 同步器、 可重入锁, 并对线程池的构造提供
了强力的支持。
线程池
 线程池的 5 中创建方式:
1、 Single Thread Executor :

只有一个线程的线程池, 因此所有提交的任务是顺序执行,
代码: Executors.newSingleThreadExecutor()


2、 Cached Thread Pool :

线程池里有很多线程需要同时执行, 老的可用线程将被新的任务触
发重新执行, 如果线程超过 60 秒内没执行, 那么将被终止并从池中删除,
代码: Executors.newCachedThreadPool()


3、 Fixed Thread Pool :

拥有固定线程数的线程池, 如果没有任务执行, 那么线程会一直等待,
代码: Executors.newFixedThreadPool(4)


在构造函数中的参数 4 是线程池的大小, 你可以随意设置, 也可以和 cpu 的核数量保持
一致, 获取 cpu 的核数量 int cpuNums = Runtime.getRuntime().availableProcessors();

4、 Scheduled Thread Pool :

用来调度即将执行的任务的线程池, 可能是不是直接执行, 每隔
多久执行一次... 策略型的
代码: Executors.newScheduledThreadPool()


5、 Single Thread Scheduled Pool :

只有一个线程, 用来调度任务在指定时间执行, 代码:
Executors.newSingleThreadScheduledExecutor()

 线程池的使用
提交 Runnable , 任务完成后 Future 对象返回 null
调用 excute,提交任务, 匿名 Runable 重写 run 方法, run 方法里是业务逻辑
见代码: ThreadPoolWithRunable
提交 Callable, 该方法返回一个 Future 实例表示任务的状态
调用 submit 提交任务, 匿名 Callable,重写 call 方法, 有返回值, 获取返回值会阻塞,一直要等
到线程任务返回结果
见代码: ThreadPoolWithcallable
 


 


 

猜你喜欢

转载自blog.csdn.net/thedarkclouds/article/details/85236861