线程安全:当多个线程访问某一个类时,这个类始终都能表现出正确的行为,那么这个类就是线程安全的
synchronized: 可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或者“临界区”
private int count = 5;
public synchronized void run(){
count--;
System.err.println(this.currentThread().getName()+" count = "+count);
}
当多个线程访问Thread的run()方法时,以排队的形式进行处理(排队是按照CPU分配的先后顺序而定的),一个线程要执行synchronized 修饰的方法里面的代码
1.尝试获得锁
2.如果拿到锁 执行synchronized 代码块内容 。如果拿不到锁 这个线程就会不断的尝试获得这把锁,直到拿到为止。而且多个线程同时去竞争这把锁,也就是会有锁竞争的问题
多个线程多个锁:每个线程都可以拿到自己的指定的锁,分别拿到锁以后执行synchronized方法体里面的内容。
synchronized关键字取得的锁都是对象锁 而不是把一段代码(方法)当成锁。
多个线程对象获得不同的锁 他们互不影响。
private static int num;
public static synchronized void printNum(String tag){
try{
if(tag.equals("a")){
num =100;
System.err.println(" tag a");
Thread.sleep(2000);
} else{
num = 200;
System.err.println("tag b");
}
} catch(Exception e){
e.printStackTrace();
}
}
在静态方法上面加synchronized 关键字 表示锁定class类,独占class类,类级别的锁
同步:synchronized 同步的概念就是共享。如果不共享资源,没有必须进行同步
异步:asynchronized 异步就是独立,相互之间不受制约。例如ajax请求
public synchronized void method1(){
try {
System.err.println(Thread.currentThread().getName());
Thread.sleep(4000);
} catch (Exception e) {
e.printStackTrace();
}
}
public void method2(){
System.err.println(Thread.currentThread().getName());
}
1.a线程先持有Object对象的lock锁 B线程如果在这个时候调用对象中的synchronized 方法则需要等待 ,也就是同步。
2.a线程先持有Object对象的lock锁 B线程可以以异步的方式调用线程对象中的非synchronized 修饰的方法 ,不需要等待。
同步的目的是为了线程安全,对于线程安全需要满足2个特性
- 原子性(同步)
- 可见性
脏读:在设计我们的程序的时候一定要考虑到问题的整体性,不然就会出现很经典的错误:脏读
public synchronized void setValue(String username, String password){
this.username = username;
this.password = password;
try{
Thread.sleep(2000);
}catch(Exception e){
e.printStackTrace();
}
System.err.println("setValue的最终结果是 username "+
username+",password"+password);
}
public void getValue(){
System.err.println("getValue方法得到username"+
username+",password"+password);
}
在对一个对象的方法加锁时候,需要考虑到业务的整体性,即在setValue/getValue方法同时加上synchronized 关键字保证业务的原子性
锁重入:当一个线程获得一个对象的锁以后 ,再次请求此对象时可以再次获得对象的锁
static class Main {
public int i = 10;
public synchronized void operationSup(){
try {
i--;
System.out.println("Main print i = " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Sub extends Main {
public synchronized void operationSub(){
try {
while(i > 0) {
i--;
System.out.println("Sub print i = " + i);
Thread.sleep(100);
this.operationSup();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当涉及到父类与子类相互执行的时候 可以使用锁重入
异常释放锁
private int i = 0;
public synchronized void operation(){
while(true){
try {
i++;
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() +
" , i = " + i);
if(i == 20){
//Integer.parseInt("a");
throw new RuntimeException();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如果一个业务分为多个子模块去执行。彼此之间相互独立,如果其中一个业务出现异常,采取的方式是记录日志,其他业务不受影响继续执行
如果一个业务分为多个子模块去执行,彼此之间是关联的,如果其中一个业务出现异常,采取的方式是抛出RuntimeException,及时终止业务。
减小锁的粒度:synchronized关键字来优化代码块的执行时间
public void doLongTimeTask(){
try {
System.out.println("当前线程开始:" +
Thread.currentThread().getName() +
", 正在执行一个较长时间的业务操作,其内容不需要同步");
Thread.sleep(2000);
synchronized(this){
System.out.println("当前线程:" +
Thread.currentThread().getName() +
", 执行同步代码块,对其同步变量进行操作");
Thread.sleep(1000);
}
System.out.println("当前线程结束:" +
Thread.currentThread().getName() +
", 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
对任意的Object对象加锁
public void method1(){
synchronized (this) { //对象锁
try {
System.out.println("do method1..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void method2(){ //类锁
synchronized (ObjectLock.class) {
try {
System.out.println("do method2..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private Object lock = new Object();
public void method3(){ //任何对象锁
synchronized (lock) {
try {
System.out.println("do method3..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
对字符串常量加锁 产生死循环
public void method() {
//new String("字符串常量")
synchronized ("字符串常量") {
try {
while(true){
System.out.println("当前线程 : " +
Thread.currentThread().getName() + "开始");
Thread.sleep(1000);
System.out.println("当前线程 : " +
Thread.currentThread().getName() + "结束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
锁对象改变 如果锁所对象本身不发生改变,即使是属性改变,那么依然是同步的。 同一对象属性的修改不会影响锁的情况
public synchronized void changeAttributte(String name, int age) {
try {
System.out.println("当前线程 : " +
Thread.currentThread().getName() + " 开始");
this.setName(name);
this.setAge(age);
System.out.println("当前线程 : " +
Thread.currentThread().getName() + " 修改对象内容为: "
+ this.getName() + ", " + this.getAge());
Thread.sleep(2000);
System.out.println("当前线程 : " +
Thread.currentThread().getName() + " 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
死锁问题:在设计程序时就应该避免双方相互持有对方的锁的情况
public void run() {
if(tag.equals("a")){
synchronized (lock1) {
try {
System.out.println("当前线程 : " +
Thread.currentThread().getName() + " 进入lock1执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("当前线程 : " +
Thread.currentThread().getName() + " 进入lock2执行");
}
}
}
if(tag.equals("b")){
synchronized (lock2) {
try {
System.out.println("当前线程 : " +
Thread.currentThread().getName() + " 进入lock2执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("当前线程 : " +
Thread.currentThread().getName() + " 进入lock1执行");
}
}
}
}