多线程--多线程高级使用--线程安全

二、修改代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4waYBXgS-1687774437259)(005-多线程高级使用-线程安全.assets/image-20210818145650463.png)]

三、运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mWNrDHz-1687774437278)(005-多线程高级使用-线程安全.assets/image-20210818151123161.png)]

四、原因分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RTSDRcAs-1687774437279)(005-多线程高级使用-线程安全.assets/image-20210818151207794.png)]

五、解决方式

1、同步互斥锁/对象监视器 synchronized

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要
解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供 了同步机制:
synchronized来解决。它可以在我们多个线程需要访问的共享资源上进行上锁,线程A没有运行完锁里面的内容
之前,线程B是无法进入的,从而保证了线程安全。

synchronized 能做到的是 让一个线程中运行顺序是阻塞的 也就是排队的

多个线程不行

2、互斥锁的使用方式

➢隐式锁

●代码块上锁
●方法上锁

➢显式锁

● lock.上锁

六、代码块加锁

1、使用方式

synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:

synchronized(同步锁){
需要同步操作的代码
}

2 、同步锁的解释

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁,同步锁的要求:
1.锁对象可以是任意类型,我们一-般 直接使用Object对象。
2.要保证这些线程对象使用的是同一把锁。
注意:
1.在任何时候最多允许一个线程拥有 同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
2.对于非static方法,同步锁就是this。对于static方 法,我们使用当前方法所在类的字节码对象(类名.class)。

3、演示

public class ThreadSafeDemo1 {
	public static void main(String[] args) {
		//构建一个Runbale的子类对象
		Thread2 thread = new Thread2();
		//创建多个Thread对象,然后让他们共同使用同一个Runnable对象
		Thread t1 = new Thread(thread,"窗口A");
		Thread t2 = new Thread(thread,"窗口B");
		Thread t3 = new Thread(thread,"窗口C");
		//启动这三个线程
		t1.start();
		t2.start();
		t3.start();
	}
}
class Thread2 implements Runnable{
	//设置票数为10张
	private int ticket = 100;
	//1、创建一个对象锁资源
	Object lock = new Object();
	@Override
	public void run() {
		//设置一个死循环,不断进行卖票的动作
		while(true) {//公平锁和非公平锁逻辑
			//2、找到会出现安全问题的代码块,进行代码块的加锁处理
			synchronized (lock) {//synchronized (new Object()) {
				//如果票数大于0,那么继续卖票
				if(ticket>0) {
					//模拟卖票的业务逻辑所需要消耗的时间
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"卖了:"+this.ticket--+"这张票");
				}else {
					break;
				}
			}
		}
	}
}

3、原理分析

synchronized关键字的参数里面所放的对象是一个锁对象, 同时也是几个多线程对象共同需要使用的对象,这个对象有个特点,一旦被其中一一个线程拿到之后,除非该线程执行完了这个锁里的逻辑代码(也可以通过其他手段,暂时先不讲),否则这个锁对象会一-直被该线程 占据,无法被释放,那么其他线程哪怕拿到了CPU的使用权,在没有拿到锁对象之前,也无法进入到该业务逻辑代码中来,只能被阻塞(BL OCKED),从而保证了业务逻辑代码在同一时间只能被一一个线程执行,保证了其数据的安全性!

4、场景模拟

现在有1-100张票分别有窗口A,B,C来进行售卖,它们都在卖这1 100张票,由于多个线程使用同-个共享资源,会导致安全问题,于是我们进行加锁,现在先由窗口A线程抢夺到了CPU使用权,然后开始执行窗口A这个线程的run方法,在运行同步代码块的时候,发现锁对象还在那,于是拿走锁对象,运行逻辑代码,一旦逻辑代码中被休眠,那么此时窗口A让出CPU使用权,然后窗口B抢夺到了CPU使用权,开始执行run方法,当窗口A运行到同步代码块的时候,发现锁对象不在那儿,没办法,只能进入阻塞状态,等待线程A释放锁对象出来,而线程A从休眠中醒来,运行完了同步代码块的代码时,把这个锁对象释放回去,那么在同步代码块处阻塞的线程一旦拿到了CPU使用权,此时就可以拿走锁对象,从而进入到同步代码块中来运行业务逻辑,没拿到的继续阻塞!

七、方法加锁

1、使用方式

使用synchronized修饰的方法就叫做同步方法,保证一个线程执行该方 法的时候,其他线程只能在
方法外等着。
格式:

public synchronized void method(){
可能会产生线程安全问题的代码
}

2、普通方法

  • 通过进行在方法结构上进行同步加锁操作
  • 1、直接把synchronized写在修饰词的位置即可,代表对整个方法进行加锁
  • 2、如果你的方法是一个普通方法的话,它的锁对象就是this
  • 3、如果你的方法是一个静态方法的话,它的锁对象就是当前类对象.class
public class ThreadSafeDemo2 {
	public static void main(String[] args) {
		//构建一个Runbale的子类对象
		Thread2 thread = new Thread2();
		//创建多个Thread对象,然后让他们共同使用同一个Runnable对象
		Thread t1 = new Thread(thread,"窗口A");
		Thread t2 = new Thread(thread,"窗口B");
		Thread t3 = new Thread(thread,"窗口C");
		//启动这三个线程
		t1.start();
		t2.start();
		t3.start();
		//
		ArrayList list = new ArrayList();
		Vector v = new Vector();
	}
}
class Thread2 implements Runnable{
	//设置票数为10张
	private static int ticket = 10;
	@Override
	public void run() {
		//设置一个死循环,不断进行卖票的动作
		while(true) {//公平锁和非公平锁逻辑
			//非静态方法的调用
			sellTicket();

		}
	}
	
	//非静态方法,在方法结构上加锁,就等同于进行使用this来在代码块上加锁
	private synchronized void sellTicket() {
		synchronized (this) {
			//如果票数大于0,那么继续卖票
			if(ticket>0) {
				//模拟卖票的业务逻辑所需要消耗的时间
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"卖了:"+this.ticket--+"这张票");
			}
		}
	}
}

3、注意

一定要注意,如果我们使用代码块进行加锁的话,那么此时
锁对象就是当前调用该方法的对象,也就是this对象!
注意:尝试在run方法里输出this,以及在main方法创建Runnable
对象的地方输出Runnable对象,看是否相同。
整个方法进行加锁的话,够直接简单,如果方法里面的代码量大
的话,不推荐这种加锁方式,因为整个方法都进入同步了,会影响效
率。

4、静态方法

public class ThreadSafeDemo2 {
	public static void main(String[] args) {
		//构建一个Runbale的子类对象
		Thread2 thread = new Thread2();
		//创建多个Thread对象,然后让他们共同使用同一个Runnable对象
		Thread t1 = new Thread(thread,"窗口A");
		Thread t2 = new Thread(thread,"窗口B");
		Thread t3 = new Thread(thread,"窗口C");
		//启动这三个线程
		t1.start();
		t2.start();
		t3.start();
		//
		ArrayList list = new ArrayList();
		Vector v = new Vector();
	}
}
class Thread2 implements Runnable{
	//设置票数为10张
	private static int ticket = 10;
	@Override
	public void run() {
		//设置一个死循环,不断进行卖票的动作
		while(true) {//公平锁和非公平锁逻辑
			//静态方法的调用
			sellStaticTicket();
		}
	}
	//静态方法
	private synchronized static void sellStaticTicket() {
		synchronized (Thread2.class) {
			//如果票数大于0,那么继续卖票
			if(ticket>0) {
				//模拟卖票的业务逻辑所需要消耗的时间
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"卖了:"+ticket--+"这张票");
			}
		}
	}

	}
}

八、lock加锁

1、使用方式

➢Lock 加锁机制是JDK1.5之后所提供的一个比synchronized代码块和synchronized方 法更广泛的锁定操作,拥有代码块/方法加锁的所有功能,除此之处更强大,更体现面向对象。
➢Lock锁也称同步锁,加锁与释放锁方法化了,如下:
●public void lock() :加同步锁。
● public void unlock() :释放同步锁。
➢Lock加锁的使用步骤
● 在成员位置使用ReentrantLock(可重入锁)类来创建一 个Lock对象
●在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
●在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁, -般情况下我们都会在finally里面调用unlock。

2、演示

public class ThreadSafeDemo3 {
	public static void main(String[] args) {
		//构建一个Runbale的子类对象
		Thread2 thread = new Thread2();
		//创建多个Thread对象,然后让他们共同使用同一个Runnable对象
		Thread t1 = new Thread(thread,"窗口A");
		Thread t2 = new Thread(thread,"窗口B");
		Thread t3 = new Thread(thread,"窗口C");
		//启动这三个线程
		t1.start();
		t2.start();
		t3.start();
	}
}
class Thread2 implements Runnable{
	//设置票数为10张
	private static int ticket = 10;
	//1、创建一个Lock对象
	Lock lock = new ReentrantLock();
	@Override
	public void run() {
		//设置一个死循环,不断进行卖票的动作
		while(true) {//公平锁和非公平锁逻辑
			//2、在出现多线程安全问题代码之前开启锁,位置和之前的synchronized代码块位置保持一致
			lock.lock();
			try {
				//如果票数大于0,那么继续卖票
				if(ticket>0) {
					//模拟卖票的业务逻辑所需要消耗的时间
					Thread.sleep(100);
					System.out.println(Thread.currentThread().getName()+"卖了:"+this.ticket--+"这张票");
				}else {
					break;
				}
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				//3、关锁操作
				lock.unlock();
			}
		}
	}
}

3、注意

lock的加锁和解锁是显式的,所以我们在进行解锁的时候,务必要注意是否在所有的条件下都完成了解锁,否则会
出现锁死的情况。

九、synchronized和lock的区别

➢在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。Lock是synchronized关键字的进阶, 掌握L ock有助于学习并发包中的源代码,在并发包中大量的类使用了Lock接口作为同步的处理方式。
➢虽然synchronized方法和语 句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。在这种场景中synchronized关键字就不那么容易实现了,使用Lock接口容易很多。
➢Lock锁可以在我们需要的地方显式的调用,或者中断,超时获取锁以及必须使用unL ock释放等更加灵活的锁操作;但是失去了synchronize隐式获取与释放的便捷性(我们大多在finally代码块中释放)。
➢lock中的锁定类是用于高级用户和高级情况的工具。-一般来说,除非您对Lock的某个高级特性有明确的需要,或者有明确的证据表明在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用synchronized。
➢在确实需要一些synchronized所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,其他场合建议用synchronized开发,直到确实证明synchronized不合适。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值