一,线程同步问题
1.Why?
为什么要线程同步?
线程对象(Runnable)中定义了全局变量,run方法会修改该变量时,如果有多个线程同时使用该线程对象,那么就会造成全局变量的值被同时修改,造成错误。看一下下面的代码:
public class CopyOfAccount implements Runnable{
private int sum = 8000;// 钱
@Override
public void run() {
// 取钱
withdraw();
}
public void withdraw() {
System.out.println("进入了withdraw");
if (5000 <= sum) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 取钱
sum = sum - 5000;
System.out.println("吐5000");
} else {
System.out.println("余额不足");
}
}
public static void main(String[] args) {
CopyOfAccount r = new CopyOfAccount();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("账户余额" + r.getSum());
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
}
输出结果:
sum=8000是Runable中的全局变量,造成了全局变量同时修改的错误。
2.What?
多个线程之间共享变量造成上述的问题,解决办法加锁。
java jdk给我们提供了synchronized关键字
使用synchronized锁定方法或代码块,同一时间内只能有一个线程访问该方法或代码块.
注:synchronized是一个非常重要的关键字。它的原理和数据库中事务锁的原理类似。我们在使用过程中,应该尽量缩减synchronized覆盖的范围,原因有二:1)被它覆盖的范围是串行的,效率低;2)容易产生死锁。
3.How?
我们看一下上述代码的修改方案:
第一种解决办法synchronized修饰代码块
public class Account implements Runnable{
private int sum = 8000;// 钱
@Override
public void run() {
// 取钱
withdraw();
}
// 一.同步方法
// 原理: 锁定当前对象 拿当前对象作为一把锁 this 1 开 0关
// JVM虚拟机先判断这把锁是开或者是关
// 两个的锁 都是 this 这两个this是同一把锁吗
// this 指向的是 正在调用该方法的对象
public void withdraw() {
System.out.println("进入了withdraw");
// 左边这个线程 一开始锁是开着的 能调用withdraw 进来马上锁上 1------->0
System.out.println(this);
synchronized (this) { //1 打开
//1------->0(锁上)
if (5000 <= sum) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 取钱
sum = sum - 5000;
System.out.println("吐5000");
} else {
System.out.println("余额不足");
}
//0-------->1 打开...
}
// 方法执行完了 在方法执行完毕后 开锁 ---->1
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
}
运行结果:
第二种解决办法:synchronized修饰方法
public synchronized void withdraw() {}
第三种办法:当然把线程共享的全局变量改成不共享的局部变量
二,线程的基本方法:
1.sleep():
在指定时间内让当前正在执行的线程暂停执行,但不会释放”锁标志”。不推荐使用。
sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
2.wait()方法
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的”锁标志”,从而使别的线程有机会抢占该锁。
当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
3.notify(),notifyAll()
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
4.yield方法
暂停当前正在执行的线程对象。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。
5.join方法
等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
public class TestJoin {
public static void main(String[] args) {
MyRunable mr=new MyRunable();
Thread t1 = new Thread(mr);
t1.start();
try {
System.out.println("t1将调用join方法");
t1.join();// 线程合并 看起来的效果 相当于 方法调用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是main线程");
}
static class MyRunable implements Runnable {
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("i="+i);
}
}
}
}
6.isAlive()
判断线程是否还”活”着(就绪、运行、阻塞),即线程是否还未终止
活着(就绪,运行,阻塞)返回true
新建(start之前),结束:返回false
7.getPriority() 获得线程的优先级数值
setPriority() 设置线程的优先级数值
sleep()和wait()区别总结一下
相同点:
都是从运行状态—-> 阻塞状态
不同点:
1.解除阻塞状态的时机不一样(1)sleep 睡醒了 自动解除阻塞
(2)wait 当调用this.notify()方法的时候 把阻塞的线程唤醒
- 是否释放锁
sleep 不会释放锁 wait 会释放锁
三.线程打断
1.线程对象.interrupt() 方法
线程睡着抛出一个InterruptedException打断它.但这种不是很好,类似泼凉水的打断 不推荐
2.线程.stop()
相当于一棒子打死了 一般不要使用,除非线程实在关不掉,这种直接不抛异常了,直接关掉线程,这种方法是不安全也是不受提倡的。
3.return
run方法执行完了线程就结束了
4.主线程 控制 子线程结束
想要在主线程中让该线程停止,如果线程t是死循环这种情况(while(true))
1.修改线程t代码(while(flag))
2.在主线程 调用 t.flag=false;
3.t线程由于flag改为false则结束
最后总结一张图
下一篇重点:死锁