JAVASE专题-线程同步

网友投稿 227 2022-09-24

JAVASE专题-线程同步

1.今日任务

线程安全线程同步死锁Lock锁等待唤醒机制

2.线程安全

2.1多线程访问临界资源

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

我们通过一个案例,演示线程的安全问题:

电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “肖申克的救赎”,本次电影的座位共100个(本场电影只能卖100张票)。 我们来模拟电影院的售票窗口,实现多个窗口同时卖 “肖申克的救赎”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。

电影票类

/** * @author bruceliu * @Description */public class Ticket implements Runnable { // 共100票 int ticket = 100; @Override public void run() { while (ticket > 0) { // 同步代码块结合对象锁 if (ticket <= 0) { return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余:" + ticket); } }}

测试类

/** * @author bruceliu * @Description */public class ThreadDemo { public static void main(String[] args) { // 创建票对象 Ticket ticket = new Ticket(); // 创建3个窗口 Thread t1 = new Thread(ticket, "窗口小姐姐A"); Thread t2 = new Thread(ticket, "窗口小姐姐B"); Thread t3 = new Thread(ticket, "窗口小姐姐C"); t1.start(); t2.start(); t3.start(); }}

运行结果发现:上面程序出现了问题

票出现了重复的票其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全

本质原因: 有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了。

解决办法: 一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待

2.2.线程同步(线程安全处理Synchronized)

java中提供了线程同步机制,它能够解决上述的线程安全问题。 线程同步的方式有两种:

方式1:同步代码块方式2:同步方法

2.2.1.同步代码块

synchronized (锁对象) { 可能会产生线程安全问题的代码}

说明: a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待 b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片 c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义。

使用同步代码块,对电影票案例中Ticket类进行如下代码修改

/** * @author bruceliu * @Description */public class Ticket implements Runnable { // 共100票 int ticket = 10; // 定义锁对象,任何对象都可以充当一个对象锁 // Object lock = new Object(); String str = new String(); @Override public void run() { while (ticket > 0) { // 同步代码块结合对象锁 synchronized (str) { if (ticket <= 0) { return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余:" + ticket); } } }}

当使用了同步代码块后,上述的线程的安全问题,解决了。

2.2.2.同步方法

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

使用同步方法,对电影票案例中Ticket类进行如下代码修改:

/** * @author bruceliu * @Description */public class Ticket implements Runnable { // 共100票 int ticket = 10; @Override public void run() { while (ticket > 0) { sellTickets(); } } // 同步方法,作用和同步代码块一样 public synchronized void sellTickets() { if (ticket <= 0) { return; } ticket--; // 在锁中进行sleep, 不会释放锁标记,其他线程仍然访问不了 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余为" + ticket); }}

同步代码块优于同步方法???为什么?

同步方法把整个方法都加锁了!粒度太大!!同步代码块可以只同步想要加锁的内容,粒度更细致! 锁的粒度越小越好!!

2.3 死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。这种情况能避免就避免掉。

synchronzied(A锁){ synchronized(B锁){ }}

示例:

public class Chinese extends Thread { @Override public void run() { synchronized (Resouce.daocha) { System.out.println(Thread.currentThread().getName()+"说:给我筷子~~"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 想 同步 筷子资源 synchronized (Resouce.kuaizi) { System.out.println(Thread.currentThread().getName()+"说:把刀叉给你~~"); } } }}

public class English extends Thread { @Override public void run() { synchronized (Resouce.kuaizi) { System.out.println(Thread.currentThread().getName() + "说:给我刀叉~~"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //代表想同步 刀叉资源 synchronized (Resouce.daocha) { System.out.println(Thread.currentThread().getName() + "说:把筷子给你~~"); } } }}

/* * 资源 */public class Resouce { public static String kuaizi="筷子"; public static String daocha="刀叉";}

/* * 测试类 */public class Test { public static void main(String[] args) { Chinese c = new Chinese(); c.setName("中国人"); c.start(); English e = new English(); e.setName("美国人"); e.start(); }}

2.4 Lock接口

我们使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对卖票案例中Ticket类进行如下代码修改:

案例一:模拟售票

/** * @author bruceliu * @Description */public class ReentrantLockDemo01 implements Runnable { // 需求:100张 // 临界资源 int count = 100; // 定义一个ReentrantLock类的对象 ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (count > 0) { // 加锁 // lock() lock.lock(); if (count <= 0) { return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余为" + count); // 解锁 // unlock() lock.unlock(); // 注意:lock()和unlock()都是成对出现的 } }}

/** * @author bruceliu * @Description 测试类 */public class TestLock { public static void main(String[] args) { ReentrantLockDemo01 r=new ReentrantLockDemo01(); Thread t0 = new Thread(r, "喜羊羊"); Thread t1 = new Thread(r, "沸羊羊"); Thread t2 = new Thread(r, "灰太狼"); Thread t3 = new Thread(r, "小灰灰"); t0.start(); t1.start(); t2.start(); t3.start(); }}

2.5等待唤醒机制

在开始讲解等待唤醒机制之前,有必要搞清一个概念——线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

等待唤醒机制所涉及到的方法:

wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

例子1: 线程通信的示例

public class Demo1 { public static void main(String[] args) { Test1 thread = new Test1(); Thread ta = new Thread(thread); ta.setName("线程AAAA"); ta.start(); //开子线程AAA Thread tb = new Thread(thread); tb.setName("线程BBBB"); tb.start(); //开子线程BBBB }}class Test1 implements Runnable { @Override synchronized public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "===" + i); if (i == 2) { try { wait(); // 退出运行状态,放弃资源锁,进入到等待队列状态 } catch (InterruptedException e) { e.printStackTrace(); } } if (i == 1) { notify(); // 从等待的序列中唤起 一个线程 } if (i == 4) { notify(); } } }}

public class Demo7 { public static void main(String[] args) { Communication com = new Communication(); Thread t1 = new Thread(com); Thread t2 = new Thread(com); t1.start(); t2.start(); }}class Communication implements Runnable { int i = 1; synchronized public void run() { while (true) { notify(); if (i <= 100) { System.out.println(Thread.currentThread().getName() + ":" + i++); } else { break; } try { wait(); } catch (InterruptedException e) { } } }}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:罗永浩做直播的公司要卖了?买主竟是家做电缆的上市公司!
下一篇:BootStrap基础
相关文章

 发表评论

暂时没有评论,来抢沙发吧~