Java多线程--1 Synchronized相关


        线程安全概念:当多个线程访问某资源(类对象或方法、变量)时,这个资源始终都能表现出正确的行为,那么这个资源(类对象或方法、变量)就是线程安全的。(任意一时刻,只能有一个线程操作该资源,则该资源是线程安全的)

        synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。


1线程安全问题

01、没有锁,也没有原子操作;线程不安全

/**
 * @author MiHai
 * @Date 2019年11月3日 下午1:16:12
 * @Desc  线程安全概念:当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,
 * 	那么这个类(对象或方法)就是线程安全的。
 * 	synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"
 *
 */
public class MyThread01 extends Thread {

	private int count = 5;

	// 加锁
	public synchronized void run() {
		count--;
		System.out.println(this.currentThread().getName() + " count = " + count);
	}

	public static void main(String[] args) {
		/**
		 * 分析:当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排对是按照CPU分配的先后顺序而定的),
		 * 一个线程想要执行synchronized修饰的方法里的代码: 1 尝试获得锁 2
		 * 如果拿到锁,执行synchronized代码体内容;拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,
		 * 而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
		 */
		MyThread01 myThread = new MyThread01();
		Thread t1 = new Thread(myThread, "t1");
		Thread t2 = new Thread(myThread, "t2");
		Thread t3 = new Thread(myThread, "t3");
		Thread t4 = new Thread(myThread, "t4");
		Thread t5 = new Thread(myThread, "t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}



02、方法加锁;线程安全

	private int count = 5;

	// 加锁
	public synchronized void run() {
		count--;
		System.out.println(this.currentThread().getName() + " count = " + count);
	}



2多个线程共用一把锁

synchronized:

1、加在方法上:

        no-static方法:表示某一个对象的锁,类的所有实现(对象)都有一把单独的锁

        static方法:表示某一个类的锁,类的所有实现(对象)共用一把锁。

2、同步块

        


1、每个对象一把锁

/**
 * @author MiHai
 * @Date 2019年11月3日 下午1:18:07
 * @Desc 
 * 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,
 * 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),
 * 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。
 *
 */
public class MyThread02 {

	private int num = 0;
	
	/** static */
	public synchronized void printNum(String tag){
		try {
			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}
			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//注意观察run方法输出顺序
	public static void main(String[] args) {
		
		//俩个不同的对象
		final MyThread02 m1 = new MyThread02();
		final MyThread02 m2 = new MyThread02();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override 
			public void run() {
				m2.printNum("b");
			}
		});		
		t1.start();
		t2.start();
	}
}



2、所有对象一把锁

/**
 * @author MiHai
 * @Date 2019年11月3日 下午1:18:07
 * @Desc 
 * 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,
 * 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),
 * 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。
 *
 */
public class MyThread02 {

	private static int num = 0;
	
	/** */
	public static  synchronized void printNum(String tag){
		try {
			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}
			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//注意观察run方法输出顺序
	public static void main(String[] args) {
		
		//俩个不同的对象
		final MyThread02 m1 = new MyThread02();
		final MyThread02 m2 = new MyThread02();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override 
			public void run() {
				m2.printNum("b");
			}
		});		
		t1.start();
		t2.start();
	}
}


3对象锁的同步和异步

对象锁的同步和异步:

同步:synchronized

同步的概念就是共享,如果不是共享的资源,就没有必要进行同步。

异步:asynchronized

异步的概念就是独立,相互直接不收到任何制约。就像在页面发起的Ajax请求,同时还可以继续浏览或操作页面的内容,两者直接没有任何关系。

同步的目的就是为了线程安全,其实对于线程安全来说,需要满足2个特点:

原子性(同步)

可见性


/**
 * @author MiHai
 * @Date 2019年11月3日 下午9:33:34
 * @Desc 对象锁的同步和异步问题
 */
public class MyThread03 {

	public synchronized void method1() {
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/** synchronized */
	public void method2() {
		System.out.println(Thread.currentThread().getName());
	}

	public static void main(String[] args) {

		final MyThread03 mo = new MyThread03();

		/**
		 * 分析: t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法
		 * t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步
		 */
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method1();
			}
		}, "t1");

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method2();
			}
		}, "t2");

		t1.start();
		t2.start();

	}

}


A线程先持有object对象的lock锁,B线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步。

A线程先持有object对象的lock锁,B线程可以以异步的方式调用对象中的非synchronized修饰的方法


即:

如果method2() 不加synchronized,则不存在锁竞争,method1()同步执行,method2异步执行,几乎同时输出结果。

如果method2() 加上synchronized,则线程A和B竞争一个对象锁。

        当A抢到锁,先输出t1,4秒后输出t2 ;当B抢到锁,先输出t2,再输出t1



4脏读(要保证业务的原子性)

业务整体需要使用完整的synchronized,保持业务的原子性。

/**
 * @author MiHai
 * @Date 2019年11月3日 下午9:59:51
 * @Desc 业务整体需要使用完整的synchronized,保持业务的原子性。
 */
public class MyThread04 {

	private String id = "001";
	private String name = "zhangsan";

	public synchronized void setValue(String id, String name) {
		this.id = id;

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		this.name = name;

		System.out.println("set 结果:id = " + id + " , name = " + name);
	}

	public void getValue() {
		System.out.println("get 结果:id = " + id + " , name = " + name);
	}

	public static void main(String[] args) throws Exception {

		final MyThread04 dr = new MyThread04(); // 脏读
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("002", "lisi");
			}
		});
		t1.start();
		Thread.sleep(1000);

		dr.getValue();
	}

}

输出:

get 结果:id = 002 , name = zhangsan

set 结果:id = 002 , name = lisi

这就出现了脏读。



这种场景下,为保证业务的原子性,在set和get方法上都加上synchronized即可。


5 synchronized锁重入

synchronized锁重入:

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程获取到了一个对象锁后,再次请求此对象是可以再次得到该对象的锁。

/**
 * synchronized的重入
 *
 */
public class ThreadDemo05{

	public synchronized void method1(){
		System.out.println("method1..");
		method2();
	}
	public synchronized void method2(){
		System.out.println("method2..");
		method3();
	}
	public synchronized void method3(){
		System.out.println("method3..");
	}
	
	public static void main(String[] args) {
		final ThreadDemo05 sd = new ThreadDemo05();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				sd.method1();
			}
		});
		t1.start();
	}
}
/**
 * synchronized的重入
 *
 */
public class SyncDubbo2 {

	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();
			}
		}
	}
	
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				Sub sub = new Sub();
				sub.operationSub();
			}
		});
		
		t1.start();
	}
	
	
}



synchronized同步出现异常,锁自动释放:

对于Web程序,异常吃放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误,比如你现在执行一个队列任务,很多对象都在等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕,就释放了锁。那么可想而知后续的对象执行的都是错误的逻辑。所以在编写代码的时候,一定要考虑周全。

/**
 * synchronized异常
 *
 */
public class SyncException {

	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();
			}
		}
	}
	
	public static void main(String[] args) {
		
		final SyncException se = new SyncException();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				se.operation();
			}
		},"t1");
		t1.start();
	}
}



6 synchronized代码块

使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待比较长的时间才能执行,这样的情况下可以使用synchronized代码块去优化代码执行的时间,也就是通常所说的减小锁的粒度。

/**
 * 使用synchronized代码块减小锁的粒度,提高性能
 *
 */
public class Optimize {

	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();
		}
	}
	
	public static void main(String[] args) {
		final Optimize otz = new Optimize();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				otz.doLongTimeTask();
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				otz.doLongTimeTask();
			}
		},"t2");
		t1.start();
		t2.start();	
	}	
}



synchronized可以使用任意的object进行枷锁,用法比较灵活。

/**
 * 使用synchronized代码块加锁,比较灵活
 *
 */
public class ObjectLock {

	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 static void main(String[] args) {
		
		final ObjectLock objLock = new ObjectLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method1();
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method2();
			}
		});
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method3();
			}
		});
		
		t1.start();
		t2.start();
		t3.start();
	}
}


特别注意的一个问题,就是不要使用String的常量加锁,会出现死循环问题。

/**
 * synchronized代码块对字符串的锁,注意String常量池的缓存功能
 *
 */
public class StringLock {

	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 static void main(String[] args) {
		final StringLock stringLock = new StringLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				stringLock.method();
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				stringLock.method();
			}
		},"t2");
		
		t1.start();
		t2.start();
	}
}


锁对象的改变问题,当时有一个对象进行枷锁的时候,要主义对象本身发生改变的时候,那么持有的锁就不同。如果对象本身不发生改变,那么依然是同步的,即使是对象的属性发生了改变。

/**
 * 锁对象的改变问题
 *
 */
public class ChangeLock {

	private String lock = "lock";
	
	private void method(){
		synchronized (lock) {
			try {
				System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");
				lock = "change lock";
				Thread.sleep(2000);
				System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
	
		final ChangeLock changeLock = new ChangeLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				changeLock.method();
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				changeLock.method();
			}
		},"t2");
		t1.start();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
	}
	
}
/**
 * 同一对象属性的修改不会影响锁的情况
 *
 */
public class ModifyLock {
	
	private String name ;
	private int age ;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	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 static void main(String[] args) {
		final ModifyLock modifyLock = new ModifyLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				modifyLock.changeAttributte("张三", 20);
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				modifyLock.changeAttributte("李四", 21);
			}
		},"t2");
		
		t1.start();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
	}
}



死锁问题

/**
 * 死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况
 *
 */
public class DeadLock implements Runnable{

	private String tag;
	private static Object lock1 = new Object();
	private static Object lock2 = new Object();
	
	public void setTag(String tag){
		this.tag = tag;
	}
	
	@Override
	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执行");
				}
			}
		}
	}
	
	public static void main(String[] args) {
		
		DeadLock d1 = new DeadLock();
		d1.setTag("a");
		DeadLock d2 = new DeadLock();
		d2.setTag("b");
		 
		Thread t1 = new Thread(d1, "t1");
		Thread t2 = new Thread(d2, "t2");
		 
		t1.start();
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
	}
}





多线程 synchronized

2020.11.18 21:02

https://www.meihaocloud.com.com/200.html , 欢迎转载,请在文章页标出原文连接 !


Copyright © 2020 千夕网 联系站长

粤公网安备 44030302001408号 粤ICP备19099833号-1