java六种线程状态:
1:首先是新建(NEW),此时还没有调用start方法
2:然后是运行(RUNNABLE)
2.1:就绪:ready:运行线程的启动start方法执行后,线程就会位于线程池中,等待着被调度
2.2:运行中,已经就绪的线程获得cpu的时间片,变为运行中。
3:阻塞(BLOCKED),线程等待获得锁
4:等待(WAITING):接收到了通知后或者是系统中断后进入等待;
5:超时(TIMED_WAITING):等待指定时间后自行返回
6:终止(TERMINATED):线程已经执行完毕
线程中的核心方法:
关于自己阅读文档后关于wait与notify/notifyAll 的理解
首先wait() 与 notify/notifyAll() 是Object类的方法,在执行两个方法时,要先获得锁。那么怎么获得锁呢?
我们先跳过这个问题说一下synchronized(对象锁)关键字获得锁的过程:
所谓对象锁,就是synchronized 给某个对象 加锁
1:synchronized可以修饰实例方法,如下形式:
1 public class MyObject {
2
3 synchronized public void methodA() {
4 //do something....
5 }
我们这里使用synchronized 关键字锁住的是当前对象。这也是称为对象锁的原因。那我们为什么要锁住当前对象呢?因为 methodA()是个实例方法,要想执行methodA(),需要以 对象.方法() 的形式进行调用obj.methodA(),obj是MyObject类的一个对象,synchronized就是把obj这个对象加锁了。
上面的代码也可以这样写:
public class MyObject {
public void methodA() {
synchronized(this){
//do something....
}
}
使用synchronized关键字同步一个明显的特点是:MyObject类中定义有多个synchronized修饰的实例方法时,若多个线程拥有同一个MyObject类的对象,则这些方法只能以同步的方式执行。即,执行完一个synchronized修饰的方法后,才能执行另一个synchronized修饰的方法。如下:
public class MyObject {
synchronized public void methodA() {
//do something....
}
synchronized public void methodB() {
//do some other thing
}
}
举例:MyObject类中有两个synchronized修饰的方法。
线程A执行methodA():
public class ThreadA extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodA();
}
}
线程B执行methodB():
public class ThreadB extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodB();
}
}
主方法:
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
//线程A与线程B 持有的是同一个对象:object
ThreadA a = new ThreadA(object);
ThreadB b = new ThreadB(object);
a.start();
b.start();
}
}
由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是必须是同步的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。
结论:
从上可以看出,本文中讲述的 synchronized 锁的范围是整个对象。如果一个类中有多个synchronized修饰的同步方法,且多个线程持有该类的同一个对象(该类的相同的对象),尽管它们调用不同的方法,各个方法的执行也是同步的。
如果各个同步的方法之间没有共享变量,或者说各个方法之间没有联系,但也只能同步执行,这会影响效率。
应用:
1 public class MyObject {
2
3 private String userName = "b";
4 private String passWord = "bb";
5
6 synchronized public void methodA(String userName, String passWord) {
7 this.userName = userName;
8 try{
9 Thread.sleep(5000);
10 }catch(InterruptedException e){
11
12 }
13 this.passWord = passWord;
14 }
15
16 synchronized public void methodB() {
17 System.out.println("userName" + userName + ": " + "passWord" + passWord);
18 }
19 }
methodA()负责更改用户名和密码。methodB()负责读取用户名和密码。
如果methodB()没有用synchronized 修饰,线程A在调用methodA()执行到第7行,更改了用户名,因某种原因(比如在第9行睡眠了)放弃了CPU。
此时,如果线程B去执行methodB(),那么读取到的用户名是线程A更改了的用户名(“a”),但是密码却是原来的密码(“bb”)。因为,线程A睡眠了,还没有来得及更改密码。但是,如果methodB()用synchronized修饰,那么线程B只能等待线程A执行完毕之后(即改了用户名,也改了密码),才能执行methodB读取用户名和密码。因此,就避免了数据的不一致性而导致的脏读问题。
回归正题:说wait() 与 notify/notifyAll 方法区别:
1:由于 wait() 与 notify/notifyAll() 是放在同步代码块中的,因此线程在执行它们时,肯定是进入了临界区中的,即该线程肯定是获得了锁的。
-
当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。
-
当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized修饰的代码块)后再释放锁。
从这里可以看出,notify/notifyAll()执行后,并不立即释放锁,而是要等到执行完临界区中代码后,再释放。所以在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出临界区。即不要在notify/notifyAll()后面再写一些耗时的代码。示例如下
public class Service {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait() ThreadName="
+ Thread.currentThread().getName());
lock.wait();
System.out.println(" end wait() ThreadName="
+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void synNotifyMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin notify() ThreadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
lock.notify();
Thread.sleep(5000);
System.out.println(" end notify() ThreadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 在第3行的testMethod()中调用 wait(),在第17行的synNotifyMethod()中调用notify()
- 从上面的代码可以看出,wait() 与 notify/notifyAll()都是放在同步代码块中才能够执行的。如果在执行wait() 与 notify/notifyAll() 之前没有获得相应的对象锁,就会抛出:java.lang.IllegalMonitorStateException异常。
- 在第8行,当ThreadA线程执行lock.wait();这条语句时,释放获得的对象锁lock,并放弃CPU,进入等待队列。
- 当另一个线程执行第23行lock.notify();,会唤醒ThreadA,但是此时它并不立即释放锁,接下来它睡眠了5秒钟(sleep()是不释放锁的,事实上sleep()也可以不在同步代码块中调用),直到第28行,退出synchronized修饰的临界区时,才会把锁释放。这时,ThreadA就有机会获得另一个线程释放的锁,并从等待的地方起(第9行)起开始执行。
举例说明:
package 任务十__多线程;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-22 21:20
*/
public class Test1 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁") {
System.out.println("t1 start");
try {
// t1 释放锁
"锁".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁") {
System.out.println("t2 start");
try {
// 通知 t1 进入等待队列
"锁".notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t2 end");
}
}
});
t1.start();
t2.start();
}
}
我们从结果可以看出这里线程1和2同时启动后,2先结束了,这是因为在代码执行到 "锁".wait();
的时候,此时就会释放对象锁:"锁"
放弃CPU,然后开启了等待,此时的线程2执行到了 "锁".notify();
的时候,就会唤醒线程1,但是此时它并不立即释放锁,它还会继续执行自己知道临界区域:也就是这个线程的结束:“}”这个符号,此时其实线程2已经执行完了输出了t2 end,然后他释放了锁,这个时候线程1就可以拿到2刚刚释放的锁,开始执行自己后续的代码
总结一下自己一开始的理解误区:锁,并不是说把一个程序给锁住不让运行了,而是说将一个程序锁住,让其他的程序不要影响了这个程序,所以说为什么一个程序在出现.wait以后进入了阻塞状态,也就是所谓的等待获得锁的这么一个状态!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/81047.html