文章目录
一、线程的上下文切换
一个CPU内核中同一时间只能运行一个线程或者一行指令,切换到另一个线程的过程称为线程的上下文切换
1.线程切换存在的问题
1.线程切换的时候如何找到上次执行的地方?
每个线程中都有各自的程序计数器,用于记录上次执行的行数
线程在上下切换时会消耗时间,性能会降低
二、线程安全(同步)问题
CPU在多个线程之间切换时,一个线程成员变量做了读的操作,线程切换之后,另一个线程对这个成员变量进行修改,就会出现线程安全问题
案例:对一些账户的余额做存取操作,存取一次后对银行总额进行计算
public class ThreadLock {
//下标表示账户,对应的value表示余额
private int[] account = new int[100];
{
//初始化数组
for (int i = 0; i < account.length; i++) {
account[i] = 10000;
}
}
public void transfer(int from, int to, int money) {
if (account[from] < money) {
throw new RuntimeException("余额不足");
}
System.out.println(Thread.currentThread().getName());
account[from] -= money;
System.out.println(from + "转账到" + to + "--" + money + "元");
account[to] += money;
System.out.println(to + "收到" + from + "--" + money + "元");
System.out.println("总账为:" + total());
}
/**
* 合计
*
* @return
*/
public int total() {
int num = 0;
for (int i = 0; i < account.length; i++) {
num += account[i];
}
return num;
}
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Random random = new Random();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
int from = random.nextInt(100);
int to = random.nextInt(100);
int money = random.nextInt(2000);
threadLock.transfer(from, to, money);
}).start();
}
}
}
会出现余额总计对不上
三、解决线程安全问题–上锁
上锁的方法:
1.同步方法
2.同步代码块
3.同步锁
1.同步方法
package com.tx.thread;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock {
//下标表示账户,对应的value表示余额
private int[] account = new int[100];
{
//初始化数组
for (int i = 0; i < account.length; i++) {
account[i] = 10000;
}
}
//在方法上加上synchronized关键字,给整个方法上锁
public synchronized void transfer(int from, int to, int money) {
if (account[from] < money) {
throw new RuntimeException("余额不足");
}
System.out.println(Thread.currentThread().getName());
account[from] -= money;
System.out.println(from + "转账到" + to + "--" + money + "元");
account[to] += money;
System.out.println(to + "收到" + from + "--" + money + "元");
System.out.println("总账为:" + total());
}
/**
* 合计
*
* @return
*/
public int total() {
int num = 0;
for (int i = 0; i < account.length; i++) {
num += account[i];
}
return num;
}
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Random random = new Random();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
int from = random.nextInt(100);
int to = random.nextInt(100);
int money = random.nextInt(2000);
threadLock.transfer(from, to, money);
}).start();
}
}
}
在方法上加上synchronized关键字,给整个方法上锁
当前线程调用方法后,后面的线程会无法执行,当前线程执行完毕后会释放锁,让其他线程进行,也就是会发生阻塞
2.同步代码块
package com.tx.thread;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock {
//下标表示账户,对应的value表示余额
private int[] account = new int[100];
{
//初始化数组
for (int i = 0; i < account.length; i++) {
account[i] = 10000;
}
}
public void transfer(int from, int to, int money) {
if (account[from] < money) {
throw new RuntimeException("余额不足");
}
//在部分关键代码上 上锁
synchronized (this){
System.out.println(Thread.currentThread().getName());
account[from] -= money;
System.out.println(from + "转账到" + to + "--" + money + "元");
account[to] += money;
System.out.println(to + "收到" + from + "--" + money + "元");
}
System.out.println("总账为:" + total());
}
/**
* 合计
*
* @return
*/
public int total() {
int num = 0;
for (int i = 0; i < account.length; i++) {
num += account[i];
}
return num;
}
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Random random = new Random();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
int from = random.nextInt(100);
int to = random.nextInt(100);
int money = random.nextInt(2000);
threadLock.transfer(from, to, money);
}).start();
}
}
}
在部分代码上加锁,synchronize(this){}这里的this表示当前类,因为只锁住了部分代码,比锁方法的效率高,也就是粒度越小,越灵活,效率越高
3.同步锁
在java.util.concurrent的并发包中有Lock接口
常见实现类:
ReentrantLock(重入锁)
writeLock(写锁)
ReadLock(读锁)
ReadWriteLock(读写锁)
基本方法
方法 | 用法 |
---|---|
lock() | 上锁 |
unlock() | 释放锁 |
1.定义同步锁对象
2.调用lock()方法上锁
3.指令执行完成后调用unlock()释放锁
*使用同步锁时,最后一定要执行unlock方法所以用try{}finally{}来让程序必须执行unlock方法
package com.tx.thread;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock {
//下标表示账户,对应的value表示余额
private int[] account = new int[100];
private Lock lock=new ReentrantLock();
{
//初始化数组
for (int i = 0; i < account.length; i++) {
account[i] = 10000;
}
}
public void transfer(int from, int to, int money) {
if (account[from] < money) {
throw new RuntimeException("余额不足");
}
//在部分关键代码上 上锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName());
account[from] -= money;
System.out.println(from + "转账到" + to + "--" + money + "元");
account[to] += money;
System.out.println(to + "收到" + from + "--" + money + "元");
}finally {
lock.unlock();
}
System.out.println("总账为:" + total());
}
/**
* 合计
*
* @return
*/
public int total() {
int num = 0;
for (int i = 0; i < account.length; i++) {
num += account[i];
}
return num;
}
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Random random = new Random();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
int from = random.nextInt(100);
int to = random.nextInt(100);
int money = random.nextInt(2000);
threadLock.transfer(from, to, money);
}).start();
}
}
}
synchronize的基本原理
一旦代码被synchronize包含,jvm会启动监视器,对这段指令进行监控,当一个线程执行到这段代码时,监视器会判断锁对象是否有其他线程持有,如果有其他线程持有,那这个线程就无法执行,只能等待所得释放, 如果没有其他线程持有,就直接执行
锁对象
可以对当前线程进行管理,如:wait等待,nodify通知
对于非静态方法:this 静态方法:类.class
乐观锁和悲观锁
悲观锁:认为锁的安全问题比较容易出现,对代码进行上锁,前边所说的同步方法,同步代码块,同步锁,都属于悲观锁,只有等到前面的线程释放锁,后面的线程才能继续执行,悲观锁的锁定和释放需要消耗时间,降低了程序的性能
乐观锁:认为锁的安全问题不容易出现,不对代码进行上锁,只会对代码做一下特殊判断
乐观锁
乐观锁的实现方式,也就是乐观锁怎么对代码做判断的
1.采用版本号机制
版本号机制就是给数据加上版本号version,每次更新数据时,版本号Version会+1,当线程做更新是会读版本号version,对比读到的版本号与数据之前的版本号是否一致,是则提交更新,否则提交失败
例如:
银行操作员对 账户(余额:100,version:1)做操作
操作员A:读取账户余额100,version:1,对其进行100-50操作,在操作员A操作的过程中,
操作员B:对其账户进行操作,读到余额:100,version:1,做100-20操作
操作员A:更新数据,余额变为 50,version+1变为2
操作员B做提交更新时,version与自己读到的版本号version不一致,更新会被驳回