小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
上篇文章说到了同步异步。那么在应用中同步异步的应用时候,设计程序时候,一定要注意业务的整体性。 也就是要注意上节学的资源的共享,则一定要同步。不然就会发生错误。比如经典的脏读(dirtyread)
脏读
概念: 对于对象的同步和异步的方法,我们在设计自己的程序时,一定要考虑问题的整体性,不然就会出现数据不一致的错误,很经典的就是脏读问题
package com.wkcto.intrinsiclock;
/**
* 脏读
* 出现读取属性值出现了一些意外, 读取的是中间值,而不是修改
之后 的值
* 出现脏读的原因是 对共享数据的修改 与对共享数据的读取不
同步
* 解决方法:
*
不仅对修改数据的代码块进行同步,还要对读取数据的代码
块同步
* Author: 老崔
*/
public class Test08 {
public static void main(String[] args) throws InterruptedException {
//开启子线程设置用户名和密码
PublicValue publicValue = new PublicValue();
SubThread t1 = new SubThread(publicValue);
t1.start();
//为了确定设置成功
Thread.sleep(100);
//在 main 线程中读取用户名,密码
publicValue.getValue();
}
//定义线程,设置用户名和密码
static class SubThread extends Thread{
private PublicValue publicValue;
public SubThread( PublicValue publicValue){
this.publicValue = publicValue;
}
@Override
public void run() {
publicValue.setValue("bjpowernode", "123");
}
}
static class PublicValue{
private String name = "wkcto";
private String pwd = "666";
public synchronized void getValue(){
System.out.println(Thread.currentThread().getName() + ",
getter -- name: " + name + ",--pwd: " + pwd);
}
public synchronized void setValue(String name, String pwd){
this.name = name;
try {
Thread.sleep(1000);
//模拟操作
name 属性需要一定时间
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pwd = pwd;
System.out.println(Thread.currentThread().getName() + ",
setter --name:" + name + ", --pwd: " + pwd );
}
}
}
复制代码
线程出现异常会自动释放锁
package com.wkcto.intrinsiclock;
/**
* 同步过程中线程出现异常, 会自动释放锁对象
*
* Author: 老崔
*/
public class Test09 {
public static void main(String[] args) {
//先创建 Test01 对象,通过对象名调用 mm()方法
Test09 obj = new Test09();
//一个线程调用 m1()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.m1();
//使用的锁对象是 Test06.class
}
}).start();
//另一个线程调用 sm2()方法
new Thread(new Runnable() {
@Override
public void run() {
Test09.sm2();
//使用的锁对象是 Test06.class
}
}).start();
}
//定义方法,打印 100 行字符串
public void m1(){
//使用当前类的运行时类对象作为锁对象,可以简单的理解
为把 Test06 类的字节码文件作为锁对象
synchronized ( Test09.class ) {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
if ( i == 50){
Integer.parseInt("abc");
//把字符串转换为
int 类型时,如果字符串不符合 数字格式会产生异常
}
}
}
}
//使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类
Test06.class 作为锁对象
public synchronized static void sm2(){
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
复制代码
死锁
package com.wkcto.intrinsiclock;
/**
* 死锁
* 在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序
不一致,可能会导致死锁
* 如何避免死锁?
*
当需要获得多个锁时,所有线程获得锁的顺序保持一致即可
* Author: 老崔
*/
public class Test10 {
public static void main(String[] args) {
SubThread t1 = new SubThread();
t1.setName("a");
t1.start();
SubThread t2 = new SubThread();
t2.setName("b");
t2.start();
}
static class SubThread extends Thread{
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
@Override
public void run() {
if ("a".equals(Thread.currentThread().getName())){
synchronized (lock1){
System.out.println("a 线程获得了 lock1 锁,还需
要获得 lock2 锁");
synchronized (lock2){
System.out.println("a 线程获得 lock1 后又
获得了 lock2,可以想干任何想干的事");
}
}
}
if ("b".equals(Thread.currentThread().getName())){
synchronized (lock2){
System.out.println("b 线程获得了 lock2 锁,还需要获得 lock1 锁");
synchronized (lock1){
System.out.println("b 线程获得lock2后又
获得了 lock1,可以想干任何想干的事");
}
}
}
}
}
}
复制代码