2023.02.28 星期二
1. 线程状态
1.1 初始状态
一个刚被创建好的线程对象,就是初始状态的对象。
Thread t = new Thread(new Runnable02());
当前新创建的t线程对象就是初始状态。
1.2 可运行状态
一个线程想要运行必须先抢到CPU。启动与运行是不同的概念。
当一个线程对象调用了start()方法启动了,但还没有抢到CPU时,都是一个可运行状态。
可运行状态的线程对象只做一件事:抢CPU。
1.3 运行状态
一个抢到CPU正在执行的线程对象。称为运行状态。
当一个运行状态的线程对象,CPU时间到期要转换为可运行状态。
1.4 终止状态
一个执行完成run()方法的线程对象,就是终止状态。
2. 线程中的常用属性和方法
2.1 name属性
用来表示一个线程对象的名称。可以在创建线程对象时指定。也可以通过方法指定。
public class NameTest {
public static void main(String[] args) {
Thread t = new Thread(new Runnbale02(),"线程1");
System.out.println(t.getName());
}
}
2.2 currentThread()
Thread类的静态方法。动态获取当前执行的线程对象。
public static native Thread currentThread()
public class Runnbale02 implements Runnable{
@Override
public void run() {
for(int i = 1;i<=10;i++) {
String name = Thread.currentThread().getName();
System.out.println(name+"-"+ i+":000");
}
}
}
2.3 sleep()
休眠方法。
哪一个线程运行过程中,遇到sleep方法。哪一个线程自己休眠。
public static native void sleep(long millis) throws InterruptedException
public class Runnbale02 implements Runnable{
@Override
public void run() {
for(int i = 1;i<=100;i++) {
String name = Thread.currentThread().getName();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"-"+ i+":000");
}
}
}
3. 阻塞状态
线程对象运行过程中,出现以下情况:
1 等待IO。做标准输入时,用户需要从控制台输入数据时。
2 sleep方法。当前线程休眠。
当线程遇到以上二种情况时,会进入阻塞状态。并会释放CPU。
4. 本地线程对象ThreadLocal类
4.1 本地线程对象是什么?
本地线程对象其实是一个容器。而且是一个只能保存一个数据的容器对象。
这个容器在每一个不同的线程中,都有一份自己的实例。
每一个线程对象都使用自己的ThreadLocal类的实例。
当出现多个线程时,每一个线程中都有一个自己使用的ThreadLocal对象。
4.2 常用方法
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();
local.set("peter");
String s = local.get();
System.out.println(s);
}
5. 案例:取款与存款案例
1 编写二个任务
一个存款
一个取款
2 专家原则
专家原则:事情哪个对象最清楚,哪个对象负责。
3 编写账号的类
public class Acount {
private Double money;
public Acount(Double money) {
this.money = money;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
}
4. 编写DAO
public class AcountDAO {
public void update(Acount acount,Double money){
acount.setMoney(money);
}
}
5. 编写Service
public class AcountService {
private AcountDAO acountDAO = new AcountDAO();
public void deposit(Acount acount,Double money){
double temp = acount.getMoney();
temp = temp + money;
acountDAO.update(acount,temp);
}
public void draw(Acount acount,Double money) throws Exception {
double temp = acount.getMoney();
if (temp > money) {
temp = temp - money;
acountDAO.update(acount, temp);
}else {
throw new Exception("账户余额不足!!!");
}
}
}
6. 编写任务类
任务类一定是与Service层对接。
public class DepositRunnable implements Runnable{
private AcountService service = new AcountService();
private Acount acount;
private Double money;
public DepositRunnable(Acount acount, Double money) {
this.acount = acount;
this.money = money;
}
@Override
public void run() {
service.deposit(acount,money);
System.out.println("存款成功!"+acount.getMoney());
}
}
public class DrawRunnable implements Runnable{
private AcountService service = new AcountService();
private Acount acount;
private Double money;
public DrawRunnable(Acount acount, Double money) {
this.acount = acount;
this.money = money;
}
@Override
public void run() {
try {
service.draw(acount,money);
System.out.println("取款成功!"+acount.getMoney());
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage()+"取款失败!"+acount.getMoney());
}
}
}
7. 编写测试类
public static void main(String[] args) {
Acount acount = new Acount(10000.0);
DepositRunnable depositRunnable01 = new DepositRunnable(acount,10000.0);
DepositRunnable depositRunnable02 = new DepositRunnable(acount,2000.0);
DrawRunnable drawRunnable01 = new DrawRunnable(acount,5000.0);
Thread t1 = new Thread(depositRunnable01,"自己");
Thread t2 = new Thread(depositRunnable02,"父母");
Thread t3 = new Thread(drawRunnable01,"女朋友");
t1.start();
t2.start();
t3.start();
}
会出现数据不一致的情况。
账户的余额会经常变化。不是一个正确的固定的数值。
6. 线程不安全
线程不安全是指多个线程在操作数据时,数据不正确的情况。
线程不安全的几个条件:
1 多个线程 - 多线程
2 同时操作 - 并发
3 同一个数据 - 临界资源
4 破坏了不可分割的操作时 - 原子性
当以上的情况发生时,就有可能造成数据的不一致。这种情况就称线程不安全。
当多线程并发操作临界资源时,破坏了其原子性操作时,就有可能造成数据不一致。
解决方案:线程同步
7. 线程同步
线程同步:多个线程以串行的方式执行。这种方式称为线程同步。
手拉手一起走,是异步。
串行的方式执行:是针对对临界资源的操作部分以串行的方式操作。
7.1 同步锁synchronized
我们使用同步锁,开保证临界资源的原子性操作不被分割。
在执行原子性操作之前,先获取同步锁对象。之后再执行原子性代码。只有原子性代码执行完成之后。我们才会释放同步锁对象。
特殊注意:只有使用同一个同步锁的多个线程,才会以串行的方式执行。
必须要保存,多个线程使用的是同一个锁对象。才能叫多个线程的同步。
一般情况下,临界资源做为锁对象更合适。
7.2 同步锁的使用方式一
synchronized(锁对象){
原子性操作代码;
}
public class DepositRunnable implements Runnable{
private AcountService service = new AcountService();
private Acount acount;
private Double money;
public DepositRunnable(Acount acount, Double money) {
this.acount = acount;
this.money = money;
}
@Override
public void run() {
synchronized (acount) {
System.out.println(Thread.currentThread().getName() + "-存款任务开始!");
service.deposit(acount, money);
System.out.println(Thread.currentThread().getName() + "-存款成功!" + acount.getMoney());
System.out.println(Thread.currentThread().getName() + "-存款任务结束!");
}
}
}
public class DrawRunnable implements Runnable {
private AcountService service = new AcountService();
private Acount acount;
private Double money;
public DrawRunnable(Acount acount, Double money) {
this.acount = acount;
this.money = money;
}
@Override
public void run() {
synchronized (acount) {
System.out.println(Thread.currentThread().getName() + "-取款开始!");
try {
service.draw(acount, money);
System.out.println(Thread.currentThread().getName() + "-取款成功!" + acount.getMoney());
} catch (Exception e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "-" + e.getMessage() + "取款失败!" + acount.getMoney());
}
System.out.println(Thread.currentThread().getName() + "-取款结束!");
}
}
}
Test类:
public class Test {
public static void main(String[] args) {
Acount acount = new Acount(10000.0);//临界资源
DepositRunnable depositRunnable01 = new DepositRunnable(acount,10000.0);
DepositRunnable depositRunnable02 = new DepositRunnable(acount,2000.0);
DrawRunnable drawRunnable01 = new DrawRunnable(acount,5000.0);
DrawRunnable drawRunnable02 = new DrawRunnable(acount,5000.0);
Thread t1 = new Thread(depositRunnable01,"自己");
Thread t2 = new Thread(depositRunnable02,"父母");
Thread t3 = new Thread(drawRunnable01,"女儿");
Thread t4 = new Thread(drawRunnable02,"老婆");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
7.3 同步锁的使用方式二
可以直接在方法上声明。
public synchronized void add();
等价于:
public class A{
public synchronized void add(){
}
}
//=========》
public class A{
public void add(){
synchronized(this){
}
}
}
A a = new A();
a.add();
a.add();
A a1 = new A();
a1.add();
这个调用的add方法与a对象调用的add方法不同步。
对于Vector类来说:多个线程同时操作同一个Vector对象。调用其add方法时,是以同步方式保存对象。称为线程安全。
7.4 同步锁的使用方式三(JDK5)
在JDK1的线程时,锁对象类型是Object。表示任意类型都可以做为锁对象。
实际开发时,一般使用临界资源对象做为锁对象。
到了JDK5,就专门创建了一个锁对象。
java.util.concurrent.locks.Lock接口
public interface Lock {
void lock();
void unlock();
}
java.util.concurrent.locks.ReentrantLock 实现类
public class ReentrantLock implements Lock, java.io.Serializable{
}
创建一个锁对象的方式
Lock lock = new ReentrantLock();
修改方式一的线程任务类
public class DepositRunnable implements Runnable {
private AcountService service = new AcountService();
private Acount acount;
private Double money;
private Lock lock;//锁对象
public DepositRunnable(Acount acount, Double money, Lock lock) {
this.acount = acount;
this.money = money;
this.lock = lock;
}
@Override
public void run() {
lock.lock();//加锁
System.out.println(Thread.currentThread().getName() + "-存款任务开始!");
service.deposit(acount, money);
System.out.println(Thread.currentThread().getName() + "-存款成功!" + acount.getMoney());
System.out.println(Thread.currentThread().getName() + "-存款任务结束!");
lock.unlock();//解锁
}
}
public class Test {
public static void main(String[] args) {
Lock lock = new ReentrantLock();//实例化锁对象
Acount acount = new Acount(10000.0);//临界资源
DepositRunnable depositRunnable01 = new DepositRunnable(acount,10000.0,lock);
DepositRunnable depositRunnable02 = new DepositRunnable(acount,2000.0,lock);
DrawRunnable drawRunnable01 = new DrawRunnable(acount,5000.0);
DrawRunnable drawRunnable02 = new DrawRunnable(acount,5000.0);
Thread t1 = new Thread(depositRunnable01,"自己");
Thread t2 = new Thread(depositRunnable02,"父母");
Thread t3 = new Thread(drawRunnable01,"女儿");
Thread t4 = new Thread(drawRunnable02,"老婆");
t1.start();
t2.start();
t3.start();
t4.start();
}
}