目录
情况3:3 1s 12 或 23 1s 1 或 32 1s 1
synchronized
synchronized 是 Java 中的关键字,是一种同步锁
它修饰的对象有以下几种:
- 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;
- 2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用 的对象是调用这个方法的对象;
- 3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的 所有对象;
- 4. 修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主 的对象是这个类的所有对象
1.线程八锁
所谓的“线程八锁”,其实就是考察 synchronized 锁住的是哪个对象
情况1:12 或 21
锁住的为同一对象,2个线程都有可能执行
@Slf4j
class Number{
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况2:1s 后12,或 2, 1s 后 1
锁住的为同一对象,2个线程都有可能执行
@Slf4j
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况3:3 1s 12 或 23 1s 1 或 32 1s 1
锁住的为同一对象,3个线程都有可能执行
注意了 c 没加锁
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
new Thread(()->{ n1.c(); }).start();
}
情况4:2, 1s 后 1
锁住的不为同一对象,不存在锁竞争,第二个线程先执行
@Slf4j
class Number{
public synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
情况5:2, 1s 后 1
锁住的不为同一对象,不存在锁竞争,第二个线程先执行,第一个锁的是类(Number.class),第二个是对象(this)
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况6:1s 后12, 或 2 1s后 1
锁住的为同一对象,2个线程都有可能执行
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n1.b(); }).start();
}
情况7:2 ,1s 后 1
锁住的不为同一对象,不存在锁竞争,第二个线程先执行
@Slf4j
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
情况8:1s 后12, 或 2 1s后 1
锁住的为同一对象,2个线程都有可能执行
class Number{
public static synchronized void a() {
sleep(1);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
线程八锁的关键:
1、非静态方法的锁默认为this,静态方法的锁为对应的Class实例
2、在某一个时刻内,只能有一个线程持有锁,无论几个线程
2.模拟买票
出现线程安全问题的是在卖票的时候可能多个线程在卖票,同时取出了共享变量count,最后的count数量就是最后那个线程处理的值。而不是共同处理的值,因为他们的执行序列发生了错误,导致count没有等待处理完就被另外一个线程先读取进来了。
import java.util.List;
import java.util.Random;
import java.util.Vector;
@Slf4j
public class Ticket {
private Integer count;
public Ticket(Integer count) {
this.count = count;
}
public Integer getCount() {
return count;
}
public Integer sell(Integer amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
static Random random =new Random();
public static Integer randomAmount(){
return random.nextInt(5) + 1;
}
}
public static void main(String[] args) {
// 模拟多人买票
Ticket ticket = new Ticket(9000);
List<Integer> list = new Vector<>();
List<Thread> threadList = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread thread = new Thread(() -> {
Integer sells = ticket.sell(randomAmount());
list.add(sells);
});
threadList.add(thread);
thread.start();
}
for (Thread thread : threadList) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(" 余票{}",ticket.getCount());
log.debug(" 卖出的票数{}",list.stream().mapToInt(i ->i).sum());
}
加锁之后
import java.util.List;
import java.util.Random;
import java.util.Vector;
@Slf4j
public class Ticket {
private Integer count;
public Ticket(Integer count) {
this.count = count;
}
public Integer getCount() {
return count;
}
public synchronize Integer sell(Integer amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
static Random random =new Random();
public static Integer randomAmount(){
return random.nextInt(5) + 1;
}
}
——————————————————————————————————————————
3.转账问题
@Slf4j
public class ExerciseTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomAmount());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomAmount());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看转账2000次后的总金额
log.debug("total:{}", (a.getMoney() + b.getMoney()));
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~100
public static int randomAmount() {
return random.nextInt(100) + 1;
}
}
// 账户
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
// 转账
public void transfer(Account target, int amount) {
synchronized(Account.class) {
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
}
这里其实会发生线程安全问题。主要就是在a转账的同时b也进去转账,那么b获取到的肯定就是没有转账的a,相对a也是。
那么直接给方法加上synchronize行不行?如果绑定的是本类对象,很明显是不行,因为是两个账户,锁是不同的,进入了不同房间。那么解决方案就是可以使用唯一的Account.class的本类,那么就能够锁住了
ReentrantLock
ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更 多的方法
1、Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能
对比synchronized
- Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
- Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象
2、使用![](https://i-blog.csdnimg.cn/blog_migrate/db81fafc5090ac605df7f2ce0c69c29f.png)
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他 线程获取,则进行等待。 采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一 般来说,使用 Lock 必须在 try{}catch{}块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
小结
Lock 和 synchronized 有以下几点不同:
1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
5. Lock 可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized