多线程锁 synchronized
1、了解 synchronized
在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问,这个锁可通过为方法添加关键字 synchronized 来获得,来保证同一对象在同一
时间只有同一线程在访问,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
2、synchronized的特点
(1)对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象
(2)使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
(3)通过synchronized无法知道有没有成功获取锁。
3、三种使用方式
(1)修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
public synchronized void increase(){ i++; }
对于当前类的对象加锁,不同对象有不同的锁,针对同一线程访问的加锁;若是两个对象,则两个对象获取不同的锁,两个线程可以同时进入该方法,以上代码的加锁就会出现线程不安全
(2)修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
public static synchronized void increase(){ i++; }
作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,即所有该类的对象获取到的都是同一把锁,因此通过class对象锁可以控制静态 成员的并发操作。
(3)修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
修饰代码块有以下三种情况:
1.对给定对象加锁
public void run() { //省略其他耗时操作.... //使用同步代码块对变量i进行同步操作,锁对象为obj synchronized(obj){ for(int j=0;j<1000000;j++){ i++; } } }
该类的不同对象获取到obj的锁是同一把锁,两个线程不能进入该方法,保证线程安全
2.对当前实例对象 this 加锁
//this,当前实例对象锁 synchronized(this){ for(int j=0;j<1000000;j++){ i++; } }
此情况和修饰实例方法的情况相同,有两个线程对象时,会出现线程不安全
3.对class对象加锁
//class对象锁 synchronized(AccountingSync.class){ for(int j=0;j<1000000;j++){ i++; } }
该类的不同对象获取到的class对象锁是同一把锁,两个线程不能进入该方法,保证线程安全
4、使用代码
public class SyncTest extends Thread { private int i=0; private String str="obj"; public void run(){ synchronized (str){ while (i<5){ i++; System.out.println(Thread.currentThread().getName()+":"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { SyncTest t1=new SyncTest(); SyncTest t2=new SyncTest(); t1.start(); t2.start(); } }
结果
Thread-0:1 Thread-0:2 Thread-0:3 Thread-0:4 Thread-0:5 Thread-1:1 Thread-1:2 Thread-1:3 Thread-1:4 Thread-1:5
线程-1是等线程-0执行完后才执行的