java 中的Synchronized

多进程多线程的选择:

当在CPU-bound(计算密集型:绝大多数时间在计算) 时最好用 - 多进程, 而在 I/O bound(I/O密集型 : IO 处理 并且 大多时间是在等待) 的时候最好用 - 多线程

同步异步:

同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。

异步则是可以提高效率了,现在cpu都是双核,四核,异步处理的话可以同时做多项工作,当然必须保证是可以并发处理的。

同步和异步最大的区别就在于。一个需要等待,一个不需要等待。
比如发短信,就是一个异步例子。发起者不关心接收者的状态。不需要等待接收者的返回信息,则可以进行下一次发送。
电话,就是一个同步例子。发起者需要等待接收者,接通电话后,通信才开始。需要等待接收者的返回信息。

Synchronized的使用

1.为什么要使用synchronized

在并发编程中存在线程安全问题,主要原因有:

1.存在共享数据

2.多线程共同操作共享数据。

关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

2、实现原理

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性

3、synchronized的三种应用方式

  1. 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  2. 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  3. 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

4、代码实现

4.1、普通同步方法

4.1.1、多个线程访问同一个对象的同一个方法

没有同步的代码与结果

public class synchronizedTest implements Runnable{
	//共享资源
    static int money =0;
    public void increase(){
    	money++;
    }
    @Override
    public void run(){
        for (int j =0 ; j<20000;j++){
            increase();
        }
    }

	public static void main(String[] args) throws InterruptedException {
		System.out.println("test synchronized");
		synchronizedTest test = new synchronizedTest();
		Thread t1 = new Thread(test);
		Thread t2 = new Thread(test);
         t1.start();  // 启动线程
         t2.start();  // 启动线程
         t1.join();  // 将该线程加入到调用线程
         t2.join();  // 将该线程加入到调用线程
         System.out.println(money);  // 等上面线程执行完成后,才到主线程来执行这行代码
	}

}
****************************
结果:
test synchronized
24918
test synchronized
18484
等等,结果都不一样,无法保证可见性。
****************************

加synchronized之后同步的代码与结果

public class synchronizedTest implements Runnable{
	//共享资源
    static int money =0;
    /**
     * synchronized 修饰实例方法
     */
    public synchronized void increase(){
    	money++;
    }
    @Override
    public void run(){
        for (int j =0 ; j<20000;j++){
            increase();
        }
    }

	public static void main(String[] args) throws InterruptedException {
		System.out.println("test synchronized");
		synchronizedTest test = new synchronizedTest();
		Thread t1 = new Thread(test);
		Thread t2 = new Thread(test);
        t1.start();  // 启动线程
        t2.start();  // 启动线程
        t1.join();  // 将该线程加入到调用线程
        t2.join();  // 将该线程加入到调用线程
        System.out.println(money);  // 等上面线程执行完成后,才到主线程来执行这行代码
	}

}

****************************
结果:
test synchronized
40000
****************************

分析:当两个线程同时对一个对象的一个方法进行操作,只有一个线程能够抢到锁。因为一个对象只有一把锁,一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,就不能访问该对象的其他synchronized实例方法,但是可以访问非synchronized修饰的方法

4.1.2、多个线程访问同一个对象的不同方法

没有同步的代码与结果

public class synchronizedTest {
    public void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }
    public void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }
    public static void main(String[] args) {
        synchronizedTest test = new synchronizedTest();

        new Thread(test::method1).start();

        new Thread(test::method2).start();
    }
}
****************************
结果:
Method 1 start
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end
****************************

加synchronized之后同步的代码与结果

public class synchronizedTest {
    public synchronized void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }
    public synchronized void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }
    public static void main(String[] args) {
        synchronizedTest test = new synchronizedTest();

        new Thread(test::method1).start();

        new Thread(test::method2).start();
    }
}
****************************
结果:
Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
****************************

分析:可以看出其他线程来访问synchronized修饰的其他方法时需要等待线程1先把锁释放

一个线程获取了该对象的锁之后,其他线程来访问其他非synchronized实例方法现象

public class synchronizedTest {
    public synchronized void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }
    public void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }
    public static void main(String[] args) {
        synchronizedTest test = new synchronizedTest();

        new Thread(test::method1).start();

        new Thread(test::method2).start();
    }
}
****************************
结果:
Method 1 start
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end
****************************

分析:当线程1还在执行时,线程2也执行了,所以当其他线程来访问非synchronized修饰的方法时是可以访问的

4.1.3、当多个线程作用于不同的对象

public class synchronizedTest {
    public synchronized void method1() {
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public synchronized void method2() {
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final synchronizedTest test1 = new synchronizedTest();
        final synchronizedTest test2 = new synchronizedTest();
        new Thread(test1::method1).start();
        new Thread(test2::method2).start();
    }

}
****************************
结果:
Method 1 start
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end
****************************

分析:因为两个线程作用于不同的对象,获得的是不同的锁,所以互相并不影响

普通同步方法总分析:

锁给的是对象。不同的对象都有各自的锁,同个对象里只有一把锁。

4.2、synchronized作用于静态方法

public class synchronizedTest implements Runnable{
	//共享资源
    static int money =0;
    /**
     * synchronized 修饰实例方法
     */
    public static synchronized void increase(){
    	money++;
    }
    @Override
    public void run(){
        for (int j =0 ; j<20000;j++){
            increase();
        }
    }
    // 如果该方法为类方法,即其修饰符为static,那么synchronized 意味着某个调用此方法的线程当前会拥有该类的锁,只要该线程持续在当前方法内运行,其他线程依然无法获得方法的使用权!
	public static void main(String[] args) throws InterruptedException {
		System.out.println("test synchronized");
		Thread t1 = new Thread(new synchronizedTest());
	    Thread t2 = new Thread(new synchronizedTest());
        t1.start();  // 启动线程
        t2.start();  // 启动线程
        t1.join();  // 将该线程加入到调用线程
        t2.join();  // 将该线程加入到调用线程
        System.out.println(money);  // 等上面线程执行完成后,才到主线程来执行这行代码
	}
}
****************************
结果:
test synchronized
40000
****************************

分析:

由例子可知,两个线程实例化两个不同的对象,但是访问的方法是静态的,两个线程发生了互斥(即一个线程访问,另一个线程只能等着),因为静态方法是依附于类而不是对象的,当synchronized修饰静态方法时,锁是class对象。

所得分配对象是类。而不是实例化的对象。

4.3、synchronized作用于同步代码块

为什么要同步代码块呢?在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。

当线程运行到该代码块内,就会拥有obj对象的对象锁,如果多个线程共享同一个Object对象,那么此时就会形成互斥!特别的,当obj == this时,表示当前调用该方法的实例对象。即
 
public void test() {
     ...
     synchronized(this) {
          // todo your code
     }
     ...
}
 
此时,其效果等同于
public synchronized void test() {
     // todo your code
}
public class synchronizedTest implements Runnable{
	//共享资源
    static int money =0;
    /**
     * synchronized 修饰实例方法
     */
    public synchronized void increase(){
    	money++;
    }
    @Override
    public void run(){
    	synchronized(this) {
    		for (int j =0 ; j<20000;j++){
                increase();
            }
    	}
    }

	public static void main(String[] args) throws InterruptedException {
		System.out.println("test synchronized");
		Thread t1 = new Thread(new synchronizedTest());
		Thread t2 = new Thread(new synchronizedTest());
        t1.start();  // 启动线程
        t2.start();  // 启动线程
        t1.join();  // 将该线程加入到调用线程
        t2.join();  // 将该线程加入到调用线程
        System.out.println(money);  // 等上面线程执行完成后,才到主线程来执行这行代码
	}
}
****************************
结果:
test synchronized
27433
****************************
public class synchronizedTest implements Runnable{
	static synchronizedTest instance=new synchronizedTest();
	//共享资源
    static int money =0;
    /**
     * synchronized 修饰实例方法
     */
    public synchronized void increase(){
    	money++;
    }
    @Override
    public void run(){
    	synchronized(this) {
    		for (int j =0 ; j<20000;j++){
                increase();
            }
    	}
    }

	public static void main(String[] args) throws InterruptedException {
		System.out.println("test synchronized");\
         //使用了静态实例化的对象,达到了同步的效果
		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
        t1.start();  // 启动线程
        t2.start();  // 启动线程
        t1.join();  // 将该线程加入到调用线程
        t2.join();  // 将该线程加入到调用线程
        System.out.println(money);  // 等上面线程执行完成后,才到主线程来执行这行代码
	}
}
****************************
结果:
test synchronized
40000
****************************
public class synchronizedTest implements Runnable{
	static synchronizedTest instance=new synchronizedTest();
	//共享资源
    static int money =0;
    /**
     * synchronized 修饰实例方法
     */
    public synchronized void increase(){
    	money++;
    }
    @Override
    public void run(){
    	synchronized(instance) {
    		for (int j =0 ; j<20000;j++){
                increase();
            }
    	}
    }

	public static void main(String[] args) throws InterruptedException {
		System.out.println("test synchronized");
        // 不同的实例化对象,但是在同步代码块上作用于静态的instance实例化对象上,其实质还是同一个对象instance的锁
		Thread t1 = new Thread(new synchronizedTest());
		Thread t2 = new Thread(new synchronizedTest());
        t1.start();  // 启动线程
        t2.start();  // 启动线程
        t1.join();  // 将该线程加入到调用线程
        t2.join();  // 将该线程加入到调用线程
        System.out.println(money);  // 等上面线程执行完成后,才到主线程来执行这行代码
	}
}
****************************
结果:
test synchronized
40000
****************************

同步代码块分析:

synchronized(instance) {

}

同步锁给了instance。

本文参考了如下资料:
https://blog.csdn.net/zjy15203167987/article/details/82531772

posted @ 2020-06-01 16:28  独孤剑—宇枫  阅读(267)  评论(0编辑  收藏  举报