线程安全
线程安全问题
线程工作的时候将数据从主存区复制到工作内存区域,处理结束后将数据写回,多个线程访问统一资源是可能产生线程安全问题
JVM内存模型
可见性:一个线程操作主存区的数据对于其他的线程是不可见的
原子性:一个线程对主存区数据的操作过程是不可分割的,不能被其他线程打断
例子
两个线程同时修改密码,输出的结果不同
class User {
private String name;
private String pass;
public User(String name, String pass) {
this.name = name;
this.pass = pass;
}
public synchronized void set(String name, String pass) {
this.name = name;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pass = pass;
System.out.println(Thread.currentThread().getName() + "-name=" + this.name + "pass=" + this.pass);
}
}
class UserServlet {
private User user;
public UserServlet() {
user = new User("zs", "123456");
}
public void setPass(String name, String pass) {
user.set(name, pass);
}
}
public class DemoThread00 {
public static void main(String[] args) {
final UserServlet us = new UserServlet();
new Thread(new Runnable() {
@Override
public void run() {
us.setPass("ls", "777777");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
us.setPass("ww", "888888");
}
}).start();
}
}
产生了姓名被覆盖的问题
Thread-0-name=wwpass=777777
Thread-1-name=wwpass=888888
syncronized关键字
**缺点:**会造成锁竞争问题
-
线程安全的概念:当多个线程访问某一个类、对象或方法时,这个类、对象或方法都能表现出与单线程执行时一致的行为,那么这个类、对象或方法就是线程安全的。
-
线程安全问题都是由全局变量及静态变量引起的。
-
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程 安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
上面的例子中set方法加入syncronized关键字可以解决问题
Thread-0-name=lspass=777777
Thread-1-name=wwpass=888888
Synchronized的作用是加锁,所有的synchronized方法都会顺序执行,(这里只占用CPU的顺序)。
Synchronized方法执行方式:
- 首先尝试获得锁
- 如果获得锁,则执行Synchronized的方法体内容。
- 如果无法获得锁则等待,并且不断的尝试去获得锁,一旦锁被释放,则多个线程会同时去尝试获得锁,造成锁竞争问题。
锁竞争问题,在高并发、线程数量高时会引起CPU占用居高不下,或者直接宕机
例子
public class DemoThread02{
private /*static*/ int count = 0;
//如果是static变量会怎样?
public /*synchronized static*/ void add() {
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+">count="+count);
}
public static void main(String[] args) {
/**
Synchronized是获得对象锁,如果作用在static类型上,则升级为类锁
*/
//内部类无法访问非final对象
/**
* 为什么要用final修饰:
*
* 内部类对象的生命周期会超过局部变量的生命周期。
* 局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡。
* 而内部类对象生命周期与其它类一样:自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才会死亡(被JVM垃圾回收)。
* 所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但匿名内部类的对象仍然活着。
*
* 匿名内部类对象可以访问同一个方法中被定义为final类型的局部变量。
* 定义为final后,编译程序的实现方法:对于匿名内部类对象要访问的所有final类型局部变量,都拷贝成为该对象中的一个数据成员。
* 这样,即使栈中局部变量已死亡,但被定义为final类型的局部变量的值永远不变,因而匿名内部类对象在局部变量死亡后,照样可以访问final类型的局部变量,因为它自己拷贝了一份,且与原局部变量的值始终一致。
*/
final DemoThread02 thread1 = new DemoThread02();
final DemoThread02 thread2 = new DemoThread02();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
thread1.add(); //1.同一个对象、同一把锁
}
}, "thread1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
thread1.add(); //1、同一个对象、同一把锁
//thread2.add();
}
}, "thread2");
t1.start();
t2.start();
}
}
同一个对象、同一把锁,都运行thread1,未加上关键字Synchronized,没有顺序执行(期望输出结果是先1后2)
thread1>count=2
thread2>count=2
加上方法关键字Synchronized
thread1>count=1
thread2>count=2
**注意:**如果没有使用同一个对象,那么起不到效果,不同对象的锁方法并不互斥,如t1线程执行o1对象实例的锁方法,t2线程执行o2对象实例的锁方法,两者不互斥
全局变量加上static,还是执行顺序同步的
Synchronized作用在非静态方法上代表的对象锁,一个对象一个锁,多个对象之间不会发生锁竞争。Synchronized作用在静态方法上则升级为类锁,所有对象对象共享一把锁,存在锁竞争。
将t2执行thread2,结果是顺序执行的
thread1>count=1
thread2>count=2
同步和异步
同步:必须等待方法执行完毕,才能向下执行,共享资源访问的时候,为了保证线程安全,必须同步。
异步:不用等待其他方法执行完毕,即可立即执行,例如Ajax异步。
对象锁只针对synchronized修饰的方法生效、 对象中的所有synchronized方法都会同步执行、而**非 synchronized方法异步执行 **
避免误区:类中有两个synchronized方法,两个线程分别调用两个方法,相互之间也需要竞争锁, 因为两个方法从属于一个对象,而我们是在对象上加锁
public class DemoThread03{
//同步执行
public synchronized void print1() {
System.out.println(Thread.currentThread().getName()+">hello!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//异步执行
public void print2() {
System.out.println(Thread.currentThread().getName()+">hello!");
}
public static void main(String[] args) {
final DemoThread03 thread = new DemoThread03();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
thread.print1();
}
}, "thread1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
thread.print2();
}
}, "thread2");
t1.start();
t2.start();
}
}
print2未加关键字则同时输出,加关键字则顺序执行一个个输出
thread1>hello!
thread2>hello!
并发脏读问题
public class DemoThread04 {
private String name = "张三";
private String address = "大兴";
public synchronized void setVal(String name, String address) {
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.address = address;
System.out.println("setValue final result:username = " + name + " , address = " + address);
}
public /*synchronized*/ void getVal() {
System.out.println("getValue:username = " + this.name + " , address = " + this.address);
}
public static void main(String[] args) throws Exception {
final DemoThread04 dr = new DemoThread04();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
dr.setVal("李四", "昌平");
}
});
t1.start();
Thread.sleep(1000);
dr.getVal();
}
}
get方法未加锁,读到脏数据
getValue:username = 李四 , address = 大兴
setValue final result:username = 李四 , address = 昌平
多个线程访问同一个资源,在一个线程修改数据的过程中,有另外的线程来读取数据,就会引起脏读的产生。
为了避免脏读我们一定要保证数据修改操作的原子性**(加锁)**、并且对读取操作也要进行同步控制
get方法加锁后
setValue final result:username = 李四 , address = 昌平
getValue:username = 李四 , address = 昌平
Oracle如何处理脏读
一个数据库有5千万条数据
线程A在9:00向数据库发起查询操作,要通过全表扫描来查询最后一条数据,运行耗时需要10分钟。
线程B在9:05项数据库发起更新操作,对最后一条数据进行了更新。
问题1:线程A得到的数据,是修改前的数据,还是修改后的数据?
答:获取修改前的数据,因为Oracle数据修改时,都会先将原数据放入到undo空间中,undo空间
可以放入多个版本的快照。
问题2:如果有多个线程对数据进行了修改,那么线程A是否还能够获取到修改前的数据?
答:可能得到也可能得不到,需要根据undo空间的大小来确定,多次修改如果覆盖了最初的undo
数据,则会返回snapshot too old异常。
synchronized锁重入
同一个线程得到了一个对象的锁之后,再次请求此对象时可以再次获得该对象的锁。
同一个对象内的多个synchronized方法可以锁重入(一个同步方法中调用了另外一个同步方法)。
public class DemoThread05{
public synchronized void run1(){
System.out.println(Thread.currentThread().getName()+">run1...");
//调用同类中的synchronized方法不会引起死锁
run2();
}
public synchronized void run2(){
System.out.println(Thread.currentThread().getName()+">run2...");
}
public static void main(String[] args) {
final DemoThread05 demoThread05 = new DemoThread05();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
demoThread05.run1();
}
});
thread.start();
}
}
执行结果如下,可以执行run2方法
Thread-0>run1...
Thread-0>run2...
父子类可以锁重入,进行sleep并没有释放锁,程序正常执行(方法中调用另一个方法)
public class DemoThread06 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Child sub = new Child();
sub.runChild();
}
});
t1.start();
}
}
class Parent {
public int i = 10;
public synchronized void runParent() {
try {
i--;
System.out.println("Parent>>>>i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Child extends Parent {
public synchronized void runChild() {
try {
while (i > 0) {
i--;
System.out.println("Child>>>>i=" + i);
Thread.sleep(100);
//调用父类中的synchronized方法不会引起死锁
this.runParent();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Child>>>>i=9
Parent>>>>i=8
Child>>>>i=7
Parent>>>>i=6
Child>>>>i=5
Parent>>>>i=4
Child>>>>i=3
Parent>>>>i=2
Child>>>>i=1
Parent>>>>i=0
抛出异常释放锁
一个线程在获得锁之后执行操作,发生错误抛出异常,则自动释放锁
public class DemoThread07 {
private int i = 0;
public synchronized void run(){
while(true){
i++;
System.out.println(Thread.currentThread().getName()+"-run>i="+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// int k = 10/0;
if (i == 10) {
throw new RuntimeException();
}
}
}
public synchronized void get(){
System.out.println(Thread.currentThread().getName()+"-get>i="+i);
}
public static void main(String[] args) throws InterruptedException {
final DemoThread07 demoThread07 = new DemoThread07();
new Thread(new Runnable() {
@Override
public void run() {
demoThread07.run();
}
},"t1").start();
//保证t1线程先执行
Thread.sleep(1000);
new Thread(new Runnable() {
@Override
public void run() {
demoThread07.get();
}
},"t2").start();
}
}
在run方法抛出异常后释放了锁,get获取到锁才能执行
t1-run>i=1
t1-run>i=2
t1-run>i=3
t1-run>i=4
t1-run>i=5
t1-run>i=6
t1-run>i=7
t1-run>i=8
t1-run>i=9
t1-run>i=10
t2-get>i=10
Exception in thread "t1" java.lang.RuntimeException
at com.mimaxueyuan.demo.base.DemoThread07.run(DemoThread07.java:41)
at com.mimaxueyuan.demo.base.DemoThread07$1.run(DemoThread07.java:55)
at java.lang.Thread.run(Thread.java:748)
添加代码int k = 10/0; 提前释放锁
Exception in thread "t1" java.lang.ArithmeticException: / by zero
at com.mimaxueyuan.demo.base.DemoThread07.run(DemoThread07.java:38)
at com.mimaxueyuan.demo.base.DemoThread07$1.run(DemoThread07.java:55)
at java.lang.Thread.run(Thread.java:748)
总结:
-
可以利用抛出异常,主动释放锁
-
程序异常时防止资源被死锁、无法释放
-
异常释放锁可能导致数据不一致
synchronized代码块
以上学习的都是synchronized作用在方法上
synchronized代码块:
可以达到更细粒度的控制
-
当前对象锁
-
类锁
-
任意对象锁
例子
public class DemoThread08 {
public void run1() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName()+">当前对象锁..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run2() {
synchronized (DemoThread08.class) {
try {
System.out.println(Thread.currentThread().getName()+">类锁..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private Object objectLock = new Object();
public void run3() {
synchronized (objectLock) {
try {
System.out.println(Thread.currentThread().getName()+">任意对象锁..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//测试方法
public static void test(final int type){
if(type==1){
System.out.println("当前对象锁测试...");
}else if(type==2){
System.out.println("类锁测试...");
}else{
System.out.println("任意对象锁测试...");
}
final DemoThread08 demo1 = new DemoThread08();
final DemoThread08 demo2 = new DemoThread08();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
if(type==1){
demo1.run1();
}else if(type==2){
demo1.run2();
}else{
demo1.run3();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
if(type==1){
demo1.run1();
}else if(type==2){
demo2.run2();
}else{
demo1.run3();
}
}
},"t2");
t1.start();
t2.start();
}
public static void main(String[] args) {
test(1);
// test(2);
// test(3);
/*
final DemoThread08 demo1 = new DemoThread08();
final DemoThread08 demo2 = new DemoThread08();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
demo1.run2();
}
},"t1");
t1.start();
//保证先去运行demo1的run2方法
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
demo2.run1();
}
},"t2");
t2.start();
*/
}
}
执行test(1)
当前对象锁测试...
t2>当前对象锁..
t1>当前对象锁..
执行test(2)
类锁测试...
t1>类锁..
t2>类锁..
执行test(3)
任意对象锁测试...
t1>任意对象锁..
t2>任意对象锁..
执行main下方的代码,几乎同时输出,说明同类型锁之间互斥,不同类型的锁之间互不干扰
t1>类锁..
t2>当前对象锁..
不要在线程内修改对象锁的引用
不要在线程内修改对象锁的引用,否则会造成锁失效问题
public class DemoThread09 {
private String lock = "lock handler";
private void method(){
synchronized (lock) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + "开始");
//锁的引用被改变,则其他线程可获得锁,导致并发问题
lock = "change lock handler";
Thread.sleep(2000);
System.out.println("当前线程 : " + Thread.currentThread().getName() + "结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final DemoThread09 changeLock = new DemoThread09();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
changeLock.method();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
changeLock.method();
}
},"t2");
t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//由于锁的引用被改变,所以t2线程也进入到method方法内执行。
t2.start();
}
}
由于中间改变锁的引用,t2也能获取到锁
当前线程 : t1开始
当前线程 : t2开始
当前线程 : t1结束
当前线程 : t2结束
在线程中修改了锁对象的属性,而不修改引用则不会引起锁失效、不会产生线程安全问题。
class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "DemoThread10 [name=" + name + ", age=" + age + "]";
}
}
public class DemoThread10 {
private Person person = new Person();
public void changeUser(String name, int age) {
synchronized (person) {
System.out.println("线程" + Thread.currentThread().getName() + "开始" + person);
// 打开注释:引起对象引用发生变化,t1、t2线程同时进入方法、导致线程安全问题
//person = new Person();
//如果只修改了值,不修改引用,锁还是生效的状态
person.setAge(age);
person.setName(name);
System.out.println("线程" + Thread.currentThread().getName() + "修改为" + person);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "结束" + person);
}
}
public static void main(String[] args) {
final DemoThread10 thread10 = new DemoThread10();
new Thread(new Runnable() {
@Override
public void run() {
thread10.changeUser("小白", 99);
}
}, "t1").start();
new Thread(new Runnable() {
@Override
public void run() {
thread10.changeUser("小黑", 100);
}
}, "t2").start();
}
}
执行结果
线程t1开始DemoThread10 [name=null, age=0]
线程t1修改为DemoThread10 [name=小白, age=99]
线程t1结束DemoThread10 [name=小白, age=99]
线程t2开始DemoThread10 [name=小白, age=99]
线程t2修改为DemoThread10 [name=小黑, age=100]
线程t2结束DemoThread10 [name=小黑, age=100]
如果执行过程中执行person = new Person(); 注释set操作
线程t1开始DemoThread10 [name=null, age=0]
线程t1修改为DemoThread10 [name=null, age=0]
线程t1结束DemoThread10 [name=null, age=0]
线程t2开始DemoThread10 [name=null, age=0]
线程t2修改为DemoThread10 [name=null, age=0]
线程t2结束DemoThread10 [name=null, age=0]
线程A修改了对象锁的引用,则线程B实际的到了新的对象锁,而不是锁被释放了,因此引发了线程安全问题。
并发与死锁
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
public class DemoThread12 {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void execute1() {
synchronized (lock1) {
System.out.println("线程" + Thread.currentThread().getName() + "获得lock1执行execute1开始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("线程" + Thread.currentThread().getName() + "获得lock2执行execute1开始");
}
}
}
public void execute2() {
synchronized (lock2) {
System.out.println("线程" + Thread.currentThread().getName() + "获得lock2执行execute2开始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("线程" + Thread.currentThread().getName() + "获得lock1执行execute2开始");
}
}
}
public static void main(String[] args) {
final DemoThread12 demo = new DemoThread12();
new Thread(new Runnable() {
@Override
public void run() {
demo.execute1();
}
}, "t1").start();
new Thread(new Runnable() {
@Override
public void run() {
demo.execute2();
}
}, "t2").start();
}
}
执行结果,输出以下内容,并且程序一直在执行,两个线程各自占据了锁lock1和锁lock2,都在等对方释放资源,造成死锁
线程t1获得lock1执行execute1开始
线程t2获得lock2执行execute2开始
线程之间的通讯
-
每个线程都是独立运行的个体,线程通讯能让多个线程之间协同工作
-
Object类中的wait/notify方法可以实现线程间通讯
-
Wait/notify必须与synchronized一同使用
-
Wait释放锁、notify不释放锁
每个线程访问一个 volatile
作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。
不使用wait/notify的通信
public class DemoThread17{
private volatile List<String> list = new ArrayList<String>();
private volatile boolean canGet = false;
public void put(){
for(int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list.add("A");
System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素");
if(i==5){
//循环到第次则通知其他线程开始获取数据进行处理
canGet = true;
System.out.println("线程"+Thread.currentThread().getName()+"发出通知");
}
}
}
public void get(){
while(true){
if(canGet){
for(String s:list){
System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s);
}
break;
}
}
}
public static void main(String[] args) {
final DemoThread17 demo = new DemoThread17();
new Thread(new Runnable() {
@Override
public void run() {
demo.put();
}
},"t1").start();;
new Thread(new Runnable() {
@Override
public void run() {
demo.get();
}
},"t2").start();
}
}
结果
线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1发出通知
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素
使用wait/notify的通信
public class DemoThread18{
//原子类
private volatile List<String> list = new ArrayList<String>();
private Object lock = new Object();
public void put(){
synchronized (lock) {
for(int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list.add("A");
System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素");
if(list.size()==5){
//数据准备好了,发出唤醒通知,但是不释放锁
lock.notify();
System.out.println("发出通知...");
}
}
}
}
public void get(){
synchronized (lock) {
try {
System.out.println("线程"+Thread.currentThread().getName()+"业务处理,发现有需要的数据没准备好,则发起等待");
System.out.println("线程"+Thread.currentThread().getName()+"wait");
lock.wait(); //wait操作释放锁,否则其他线程无法进入put方法
System.out.println("线程"+Thread.currentThread().getName()+"被唤醒");
for(String s:list){
System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final DemoThread18 demo = new DemoThread18();
new Thread(new Runnable() {
@Override
public void run() {
demo.get();
}
},"t1").start();;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
demo.put();
}
},"t2").start();
}
}
结果如下,notify不释放锁
线程t1业务处理,发现有需要的数据没准备好,则发起等待
线程t1wait
线程t2添加第0个元素
线程t2添加第1个元素
线程t2添加第2个元素
线程t2添加第3个元素
线程t2添加第4个元素
发出通知...
线程t2添加第5个元素
线程t2添加第6个元素
线程t2添加第7个元素
线程t2添加第8个元素
线程t2添加第9个元素
线程t1被唤醒
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
notify与notifyAll的区别
Notify只会通知一个wait中的线程,并把锁给他,不会产生锁竞争问题,但是该线程处理完毕之后 必须再次notify或notifyAll,完成类似链式的操作。
NotifyAll会通知所有wait中的线程,会产生锁竞争问题。
public class DemoThread22 {
public synchronized void run1() {
System.out.println("进入run1方法..");
// this.notifyAll();
this.notify();
System.out.println("run1执行完毕,通知完毕..");
}
public synchronized void run2() {
try {
System.out.println("进入run2方法..");
this.wait();
System.out.println("run2执行完毕..");
// this.notify();
// System.out.println("run2发出通知..");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void run3() {
try {
System.out.println("进入run3方法..");
this.wait();
System.out.println("run3执行完毕..");
// this.notify();
// System.out.println("run3发出通知..");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
final DemoThread22 demo = new DemoThread22();
new Thread(new Runnable() {
@Override
public void run() {
demo.run2();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.run3();
}
}).start();
Thread.sleep(1000L);
new Thread(new Runnable() {
@Override
public void run() {
demo.run1();
}
}).start();
}
}
run1中使用notify方法,run2和run3都在wait,执行结果如下,run3还未执行,程序未终止(notify只能通知一个线程)
进入run2方法..
进入run3方法..
进入run1方法..
run1执行完毕,通知完毕..
run2执行完毕..
在run2中也notify,链式通知,始终只有一个线程获得锁
进入run2方法..
进入run3方法..
进入run1方法..
run1执行完毕,通知完毕..
run2执行完毕..
run2发出通知..
run3执行完毕..
如果在run2和run3中都不执行notify,而是在run中notifyAll,可以通知到所有的线程(会引发锁竞争)
进入run2方法..
进入run3方法..
进入run1方法..
run1执行完毕,通知完毕..
run3执行完毕..
run2执行完毕..
阻塞式线程安全队列实现
-
如果队列满了不能报错,而是执行wait
-
如果get没有数据,也要执行wait状态
-
线程安全:不允许多个线程同时写,不能在put的同时进行get(避免脏读)
class MQueue {
private List<String> list = new ArrayList<String>();
private int maxSize;
private Object lock = new Object();
public MQueue(int maxSize){
this.maxSize=maxSize;
System.out.println("线程"+Thread.currentThread().getName()+"已初始化长度为"+this.maxSize+"的队列");
}
public void put(String element){
synchronized (lock) {
if(this.list.size()==this.maxSize){
try {
System.out.println("线程"+Thread.currentThread().getName()+"当前队列已满put等待...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.list.add(element);
System.out.println("线程"+Thread.currentThread().getName()+"向队列中加入元素:"+element);
lock.notifyAll(); //通知可以取数据
}
}
public String take(){
synchronized (lock) {
if(this.list.size()==0){
try {
System.out.println("线程"+Thread.currentThread().getName()+"队列为空take等待...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String result = list.get(0);
list.remove(0);
System.out.println("线程"+Thread.currentThread().getName()+"获取数据:"+result);
lock.notifyAll(); //通知可以加入数据
return result;
}
}
}
public class DemoThread20 {
public static void main(String[] args) {
final MQueue q = new MQueue(5);
new Thread(new Runnable() {
@Override
public void run() {
q.put("1");
q.put("2");
q.put("3");
q.put("4");
q.put("5");
q.put("6");
}
},"t1").start();
new Thread(new Runnable() {
@Override
public void run() {
q.put("11");
q.put("21");
q.put("31");
q.put("41");
q.put("51");
q.put("61");
}
},"t2").start();
new Thread(new Runnable() {
@Override
public void run() {
q.take();
q.take();
q.take();
q.take();
q.take();
}
},"t3").start();
new Thread(new Runnable() {
@Override
public void run() {
q.take();
q.take();
q.take();
q.take();
q.take();
}
},"t4").start();
}
}
执行结果
线程main已初始化长度为5的队列
线程t1向队列中加入元素:1
线程t1向队列中加入元素:2
线程t1向队列中加入元素:3
线程t1向队列中加入元素:4
线程t1向队列中加入元素:5
线程t1当前队列已满put等待...
线程t2当前队列已满put等待...
线程t3获取数据:1
线程t3获取数据:2
线程t3获取数据:3
线程t3获取数据:4
线程t3获取数据:5
线程t2向队列中加入元素:11
线程t1向队列中加入元素:6
线程t2向队列中加入元素:21
线程t2向队列中加入元素:31
线程t2向队列中加入元素:41
线程t2当前队列已满put等待...
线程t4获取数据:11
线程t4获取数据:6
线程t4获取数据:21
线程t4获取数据:31
线程t4获取数据:41
线程t2向队列中加入元素:51
线程t2向队列中加入元素:61
wait方法解析
wait和notify为Object类中的方法。而join方法为Thread类直接提供的方法。等待时间到了如果没有被唤醒也继续执行
public final void wait() throws InterruptedException
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException {
wait(0);
}
调用
public final native void wait(long timeout) throws InterruptedException;
//进行舍入,只要纳秒值大于0就进位
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
//调用本地接口
wait(timeout);
}
例子
public class WaitDemo0 {
public static void main(String[] args) {
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
System.out.println(Thread.currentThread().getName()+" get Lock, waiting 2000 million");
try {
lock.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" release Lock,run over");
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//提醒:修改这个数值的大小,来观察t1线程wailt(timeout)的效果
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock){
System.out.println(Thread.currentThread().getName()+" get Lock, notify");
lock.notify();
System.out.println(Thread.currentThread().getName()+" release Lock,run over");
}
}
}, "t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("final:"+t1.getName()+" and " + t2.getName() + " run over!");
System.out.println("t1's state:"+t1.getState());
System.out.println("t2's state:"+t2.getState());
}
}
执行结果
t1 get Lock, waiting 2000 million
t1 release Lock,run over
t2 get Lock, notify
t2 release Lock,run over
final:t1 and t2 run over!
t1's state:TERMINATED
t2's state:TERMINATED
修改t2 sleep 1秒
t1 get Lock, waiting 2000 million
t2 get Lock, notify
t2 release Lock,run over
t1 release Lock,run over
final:t1 and t2 run over!
t1's state:TERMINATED
t2's state:TERMINATED
守护线程和用户线程
线程分类:daemon线程(守护线程)、user线程(用户线程)
易混淆知识点:main函数所在的线程就是一个用户线程
重要知识点1:最后一个user线程结束时,JVM会正常退出,不管是否有守护线程正在运行。反过来说:只要有一个用户线程还没结束,JVM 进程就不会结束。
重要知识点2:父线程结束后,子线程还可以继续存活,子线程的生命周期不受父线程的影响
/**
* 守护线程和用户线程Demo
*/
public class DaemonAndUserThreadDemo0 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
}
}
});
//提醒:打开和关闭这个设置观察JVM进程是否终止
//thread.setDaemon(true);
thread.start();
//输出线程是否为守护线程
System.out.println(thread.getName() +" is daemon? "+ thread.isDaemon());
System.out.println(Thread.currentThread().getName() +" is daemon? "+ Thread.currentThread().isDaemon());
System.out.println("main is over");
}
}
执行结果,while死循环,用户线程不结束,JVM进程不结束(main线程结束了,但是它创建的子线程thread依然存活)
Thread-0 is daemon? false
main is daemon? false
main is over
修改thread为守护线程,mian函数是唯一的用户线程,执行完毕后JVM进程就停止了
Thread-0 is daemon? true
main is daemon? false
main is over
Java虚拟机启动时会启动一个DestroyJavaVM的线程,在所有的用户线程结束后终止JVM进程
tomcat中提供的接收和处理的线程都是守护线程,执行shutdown命令时会等待用户线程关闭,而kill是强行关闭
上下文切换
当前线程使用完时间片后就会进入就绪状态,让出CPU执行权给其他线程,此时就是从当前线程的上下文切换到了其他线程。 当发生上下文切换的时候需要保存执行现场,待下次执行时进行恢复。 所以频繁的、大量的上下文切换会造成一定资源开销