目录
锁
synchronized
有三种方式来加锁,分别是:
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 静态方法,作用于当前类对象加锁,进入同步代码前要获得的当前类对象的锁
- 代码块,指定加锁对象,对给定对象加锁,进入同步代码块之前要获得给定对象的锁
- 实例方法:调用该方法的实例
- 静态方法:类对象
- this:调用该方法的实例对象(类对象 实例)
- 类对象:类对象
public class Ch01 {
public static void main(String[] args) {
// 同步代码块
// 创建一个对象
// 类对象
// 当前实例this
// 同步监视器
synchronized (Ch01.class) {
int a = 1;
}
}
}
操作共享数据的代码
共享数据:多个线程共同操作的变量,都可以充当锁
当使用同步方法时,synchronized锁的东西是this(默认的)
同步方法
有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
处理实现runnable接口方式的线程安全问题
synchronized(同步监视器){
// 需要被同步的代码:操作共享数据的代码,或理解为多个线程共同操作的变量
}
同步监视器:锁。任何类的对象都可以充当锁,但是多个线程必须要共用同一把锁。
处理继承Thread类方式的线程安全问题
public static Object obj = new Object();
同步代码块
有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
synchronized(object){ }
案例——卖票
定义一个Ticket类。创建一个初始的票数,假如说是50张,然后重写run方法。创建一个死循环,让顾客一直买票。如果,票数等于0,输出卖空,然后跳出循环。每卖出一张票,count就--,然后获取当前对象的名称输出剩余的票数。如果这样输出就会发现,有问题,可能会有两个窗口同事都有50张票和会有负数票的情况,因为没加锁。
package night;
public class Ticket implements Runnable {
private int count = 50;
@Override
public void run() {
while (true) {
if (count == 0) {
System.out.println("卖完");
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "剩余:" + count);
}
}
}//end run
}//end Ticket
加锁:
package night;
public class Ticket implements Runnable {
private int count = 50;
private Object Lock = new Object();
@Override
public void run() {
while (true) {
synchronized (Lock) {
if (count == 0) {
System.out.println("卖完");
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "剩余:" + count);
}
}
}
}//end run
}//end Ticket
输出:
package night;
public class Demo {
public static void main(String[] args) {
Ticket ticket=new Ticket();
Thread t1=new Thread(ticket,"窗口一");
t1.start();
Thread t2=new Thread(ticket,"窗口二");
t2.start();
Thread t3=new Thread(ticket,"窗口三");
t3.start();
}
private static void demo(){
}
}
死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
java死锁产生的四个必要条件
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
原文链接:https://blog.csdn.net/qq_31762741/article/details/123274086
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
public class DeadLock {
//创建两个对象
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 获取锁a");
}
}
},"B").start();
}
}
volatile关键字的使用
主要作用:是使变量在多个线程间可见。
强制用公共堆栈中取的变量的值,而不是从线程私有的数据栈中取得变量的值。
如何防范死锁?
1.尽量避免使用多个锁。
2.规范的使用多个锁,并设计好锁的获取顺序。
3.随用随放。即是,手里有锁,如果还要获得别的锁,必须释放全部资源才能各取所需。
4.规范好循环等待条件。比如,使用超时循环等待,提高程序可控性
线程通信
多个线程的使用
/*
线程通信
A先走,B等待,A走完唤醒B ,B走
*/
线程的常用方法
Thread类中的方法
1.start:启动当前线程;执行run方法
2.run
3.currentThread:静态方法,获取当前正在执行的线程
4.getId():返回此线程的唯一标识
5.setName(String):设置当前线程的name
6.getName():获取当前线程的name
7.getPriority():获取当前线程的优先级
8.setPriority(int):设置当前线程的优先级
9.getState():获取当前线程的声明周期
10.interrupt():中断线程的执行
11.interrupted():查看当前线程是否中断
面试题
sleep和wait方法的区别
sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间
不同点:在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器,wait 方法会放弃这个对象的监视器