java 中的Synchronized
多进程多线程的选择:
当在CPU-bound(计算密集型:绝大多数时间在计算) 时最好用 - 多进程, 而在 I/O bound(I/O密集型 : IO 处理 并且 大多时间是在等待) 的时候最好用 - 多线程。
同步异步:
同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。
异步则是可以提高效率了,现在cpu都是双核,四核,异步处理的话可以同时做多项工作,当然必须保证是可以并发处理的。
同步和异步最大的区别就在于。一个需要等待,一个不需要等待。
比如发短信,就是一个异步例子。发起者不关心接收者的状态。不需要等待接收者的返回信息,则可以进行下一次发送。
电话,就是一个同步例子。发起者需要等待接收者,接通电话后,通信才开始。需要等待接收者的返回信息。
Synchronized的使用
1.为什么要使用synchronized
在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
2、实现原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
3、synchronized的三种应用方式
- 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
- 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
- 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
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