一、多线程访问临界资源
临界资源 :多个线程同时访问的资源。
产生原因:有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了,就会产生线程安全问题。
例如:多个窗口共同出售100张票问题。当第一窗口准备出售第一张票时,电脑卡了,这时窗口二显示第一张票还未售出,所以窗口二将第二张票售出了,此时一窗口电脑好了,继续卖票,将第一张票卖出。这就产生了问题。
二、如何解决线程安全问题
解决方案:一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待。
语法:
synchronized(锁) {
//需要访问临界资源的代码段
}
说明:
a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义
同步代码块
public class Ticket implements Runnable{
// 需求:100张
// 临界资源
private int ticket = 100;
@Override
public void run() {
while (true) {
//上锁
synchronized(this){
if (ticket < 1) {
break;
}
System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
ticket--;
}
}
}
}
同步方法:
//非静态方法
public class Ticket implements Runnable{
// 需求:100张
// 临界资源
private int ticket = 100;
@Override
public void run() {
while (true) {
if(!sale()){
break;
}
}
}
public synchronized boolean sale(){//锁是this
if (ticket < 1) {
return false;
}
System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
ticket--;
return true;
}
}
//静态方法
public class Ticket implements Runnable{
// 需求:100张
// 临界资源
private static int ticket = 100;
@Override
public void run() {
while (true) {
if(!sale()){
break;
}
}
}
public synchronized static boolean sale(){ //锁是 类.class
if (ticket < 1) {
return false;
}
System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
ticket--;
return true;
}
}
重入锁
从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
注意:最好将 unlock的操作放到finally块中
通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
//使用重入锁
private static volatile SingleTon singleTon=null ;
private static ReentrantLock lock = new ReentrantLock();
private SingleTon(){
synchronized (SingleTon.class) {
if(singleTon!=null){
throw new RuntimeException("禁止反射破解!");
}
}
}
public static SingleTon getInstance(){
lock.lock();
try {
if(singleTon==null){
singleTon = new SingleTon();
}
return singleTon;
}finally {
lock.unlock();
}
}
二、死锁
每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
死锁的条件:
1、两个以上的线程
2、至少两个锁以上
3、同步中嵌套同步
三、线程通信
在jdk1.5之前有三个方法实现线程通信:
wait(): 等待,线程执行这个方法进入等待队列(和锁有关,一个锁对应一个等待队列), 需要被唤醒
notify(): 通知唤醒,从等待队列中随机唤醒一个线程
notifyAll():全部唤醒,把等待队列中所有的线程都唤醒
当多个线程都进入一个等待队列,在使用notifyAll的时候,会全部唤醒,浪费资源。这时我们可以创建多个等待队列,让等待线程分类进入,唤醒的时候只需要唤醒我们需要的队列就行,这样大大提高了效率。
ReentrantLock lock = new ReentrantLock();
Condition rl = lock.newCondition(); //创建第一个等待队列
Condition wl = lock.newCondition(); //创建第二个等待队列
try {
rl.await(); //线程放入第一个等待队列
} catch (InterruptedException e) {
e.printStackTrace();
}
wl.signal(); //唤醒第二个等待队列中的线程
四、线程池
为什么需要线程池:
例如有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样会频繁的创建和销毁线程。频繁创建和销毁线程会比较耗性能。如果有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。
//创建容量为4的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(4);
//给线程池添加线程
threadPool.submit("线程");
//关闭线程池
threadPool.shutdown();
五、读写锁
ReadWriteLock接口:可实现多个线程"同时"读写操作,
读/写 写/写互斥
读/读 不互斥
其实现类中ReentrantReadWriteLock 中有readLock和writeLock方法 再实现上锁和解锁