当使用多线程访问同一个资源时,非常容易出现线程安全的问题,例如,当多个线程同时对一个数据进行修改时,会导致某些线程对数据的修改丢失。
线程安全的前提:
1.保证原子性
2.保证顺序性
3.保证可见性
为了保证以上能够完成,采用了同步机制来解决问题。
一.synchronized关键字-监视器锁
1.原理
ynchronized的底层是使用操作系统的mutexlock实现的。
*当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
*当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量
*synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。
2.synchronized关键字的用法
在Java中,每一个对象都有一个对象锁与之关联,该锁表明对象在任何时候只允许被一个线程所拥有,当一个线程在调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码,在这个线程执行时,其他线程不得访问,执行结束后,才释放锁,其他线程才能去抢夺这把锁,然后执行代码。这就保证了不会发生多个线程抢夺共享资源的问题,synchronized主要有两种使用 方法
第一种:synchronized方法,在方法申明前加入synchronized关键字。
public synchronized static void method1(){...}//修饰静态方法
public synchronized void method2(){...}//修饰非静态方法
第二种:synchronized块,将需要加同步锁的代码用synchronized块包裹住。
public void method3(){
synchronized(obj){//括号里申明指定上锁的对象
.......//do something
}
}
3.弄清楚synchronized锁住的对象是哪个
synchronized锁住对象的所有同步的地方都会同步互斥。
1.修饰块
A.synchronized(this){//同步代码块}
锁住的是this所指代的对象,当所有任意一个线程在执行这段同步代码块时,其他任意访问this所代表的对象的线程都会被阻塞,直到这段同步代码块执行完成,才会释放锁,其他线程才能操作this对象
public class T {
public void method1(){
synchronized (this){
System.out.println(Thread.currentThread().getName());
while (true){}
}
}
public void method2(){
synchronized (this){
System.out.println(Thread.currentThread().getName());
while (true){}
}
}
public static void main(String[] args) {
T t1=new T();
T t2=new T();
new Thread(new Runnable() {
@Override
public void run() {
t1.method1();//这里this指代是t1
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
t1.method2();
}
},"线程2").start();
new Thread(new Runnable() {
@Override
public void run() {
t2.method1();//这里this指代的是t2
}
},"线程3").start();
new Thread(new Runnable() {
@Override
public void run() {
t2.method2();
}
},"线程4").start();
}
}
因为锁的是当前this的对象,只会对同一个对象起互斥作用,不是同一个对象synchronized就不起作用,所以所以输出结果只能是"线程1"和"线程2"中的一个,"线程3"和"线程4"中的一个。
线程1
线程3
Process finished with exit code -1
B.synchronized(object){//同步代码块}
同上锁住的是obj对象,这样当某个线程正在执行这段代码时,其他线程只是会被obj阻塞,但是仍然可能访问T类对象的其他方法
public class T {
public static void method1(){
System.out.println(Thread.currentThread().getName());
while (true){}
}
public static void main(String[] args) {
T t1=new T();
T t2=new T();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (t1) {
method1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (t1) {
method1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (t2){
method1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (t2){
method1();
}
}
}).start();
}
}
因为只会对synchronized()里的对象堵塞,所以结果是
线程1
线程4
Process finished with exit code -1
C.synchronized(T.class){//同步代码块}
锁住T的所有对象,只要任意一个线程在访问T,其他线程在访问时都会被阻塞住,包括所有实例对象的方法访问和T的静态成员或静态方法,这种方式效率比较低,在不需要锁住所有实例,所有方法的场景,不需要使用这种方式,在多线程资源竞争恶劣的情况下,会大大降低多线程的效率。
public class T {
public static void method1(){
System.out.println(Thread.currentThread().getName());
while (true){}
}
public static void method2(){
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
T t1=new T();
T t2=new T();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (T.class) {
method1();
}
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (T.class) {
method2();
}
}
},"线程2").start();
}
}
因为锁住的是T类这个所有对象,所以结果是
线程1
Process finished with exit code -1
2.修饰方法
A.修饰静态方法,锁住的是这个类的所有对象。
public class T {
public synchronized static void method1(){
System.out.println(Thread.currentThread().getName());
while(true){
}
}
public synchronized static void method2(){
System.out.println(Thread.currentThread().getName());
while(true){
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
method1();
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
method2();
}
},"线程2").start();
}
}
尽管两个线程执行的不是同一个T类里的同一方法,但此时synchronized修饰的是静态方法,锁的是这个类的所有对象,所以输出结果只能是两个中的一个
线程2
Process finished with exit code -1
B.修饰非静态方法,锁住的是正在执行这个同步方法的对象
public class T {
public synchronized void method1(){
System.out.println(Thread.currentThread().getName());
while(true){}
}
public synchronized void method2(){
System.out.println(Thread.currentThread().getName());
while(true){}
}
public synchronized void method3(){}
public static void main(String[] args) {
T t1=new T();
T t2=new T();
new Thread(new Runnable() {
@Override
public void run() {
t1.method1();
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
t1.method2();
}
},"线程2").start();
new Thread(new Runnable() {
@Override
public void run() {
t2.method1();
}
},"线程3").start();
new Thread(new Runnable() {
@Override
public void run() {
t2.method2();
}
},"线程4").start();
}
}
因为锁的是执行当前同步方法的对象。 并不会对其他的对象起互斥效果,所以输出结果只能是"线程1"和"线程2"中的一个,"线程3"和"线程4"中的一个。
线程1
线程4
Process finished with exit code -1
从以上的示例中我们可以看出来synchronized(this){}和synchronized修饰非静态方法是等效的,锁的是线程访问的同一对象;synchronized(T.class){}与synchronized修饰静态方法是等效的,锁的是T类的所有对象。而synchronized(object){}使用更加灵活,锁的是自己指定的对象。