第3 章 : 线程的同步与死锁
14 同步问题引出
Thread描述每一个线程对象
Runnable描述多个线程操作的资源
多个线程访问同一资源的时候,如果处理不当会产生数据错误
3个线程卖票程序,会出现多张同号的票
class MyThread implements Runnable { private int ticket = 10; @Override public void run() { while (true) { if (this.ticket > 0) { System.out.println( Thread.currentThread().getName() + "卖第" + this.ticket + " 张票" ); this.ticket--; } else { System.out.println("票卖光了"); break; } } } } public class Demo { public static void main(String[] args) { MyThread thread = new MyThread(); new Thread(thread).start(); new Thread(thread).start(); new Thread(thread).start(); // 5 } }
15 线程同步处理
同步:多个操作在同一时间段内只能有一个线程进行,
其他线程要等待此线程完成之后才可以继续还行
解决同步问题的方式是锁
synchronized定义同步方法或同步代码块,里边的代码只允许一个线程执行
加入同步之后,程序整体性能下降了
1、同步代码块
synchronized(同步对象){}
举例
synchronized (this) { if (this.ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖第" + this.ticket + " 张票"); this.ticket--; } else { System.out.println("票卖光了"); break; } }
2、同步函数
public synchronized boolean method(){}
举例
public synchronized boolean sale(){ if (this.ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖第" + this.ticket + " 张票"); this.ticket--; return true; } else { System.out.println("票卖光了"); return false; } }
16 线程死锁
死锁是在进行多线程同步处理之中有可能产生的一种问题
是指若干个线程彼此互相等待的状态
若干线程访问同一资源时,一定要进行同步处理
而过多的同步会造成死锁
public class Demo { public static void main(String[] args) { //o1 o2 代表资源 Object o1 = new Object(); Object o2 = new Object(); System.out.println("go go go!"); Thread t1 = new Thread(new Runnable() { public void run() { synchronized (o1) { //线程t1获取o1的锁才能继续执行 try { Thread.sleep(3000); //睡3秒,确保线程t2把o2锁拿走 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1获得了哦O1"); synchronized (o2) { //线程t1获取o2的锁才能继续执行 System.out.println("t1获得了哦O2"); } } } }); Thread t2 = new Thread(new Runnable() { public void run() { synchronized (o2) { //线程t2获取o2的锁才能继续执行 try { Thread.sleep(3000); //睡3秒,确保线程t1把o1锁拿走 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2获得了哦O2"); synchronized (o1) { //线程t2获取o1的锁才能继续执行 System.out.println("t2获得了哦O1"); } } } }); t1.start(); t2.start(); //启动线程 } }
第4 章 : 综合实战:“生产者-消费者”模型
17 生产者与消费者基本程序模型
生产者负责信息内容生产
消费者取走信息
消费者要等待生产者生产完成再取走
生产者需要等待消费者消费完成再生产
不加锁示例
class Message { private String content; public void setContent(String content) { this.content = content; } public String getContent() { return content; } } class Producer implements Runnable { private Message message; private static int count; public Producer(Message message) { this.message = message; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.message.setContent("这是第" + count + " 个消息"); count++; } } } class Consumer implements Runnable { private Message message; public Consumer(Message message) { this.message = message; } @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.message.getContent()); } } } class Demo { public static void main(String[] args) { Message message = new Message(); new Thread(new Producer(message)).start(); new Thread(new Consumer(message)).start(); } } /** 这是第0 个消息 这是第0 个消息 这是第1 个消息 这是第2 个消息 这是第3 个消息 这是第4 个消息 这是第5 个消息 这是第6 个消息 这是第7 个消息 这是第8 个消息 */
18 解决生产者-消费者同步问题
增加关键字 synchronized
19 利用Object类解决重复操作
等待机制
(1)一直等待
public final void wait()
(2)等待一段时间
public final native void wait(long timeout)
唤醒线程
(1)唤醒一个等待线程, 唤醒第一个等待的线程
public final native void notify();
(2)唤醒全部等待线程,谁优先级高谁先执行
public final native void notifyAll();
完整代码
class Message { private String content; private boolean flag = false; // 生产完成就为true public synchronized void setContent(String content) { if (this.flag == true) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.content = content; this.flag = true; notify(); } public synchronized String getContent() { if (this.flag == false) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } try { return content; } finally { this.flag = false; notify(); } } } class Producer implements Runnable { private Message message; private static int count; public Producer(Message message) { this.message = message; } @Override public void run() { for (int i = 0; i < 10; i++) { this.message.setContent("这是第" + count + " 个消息"); count++; } } } class Consumer implements Runnable { private Message message; public Consumer(Message message) { this.message = message; } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(this.message.getContent()); } } } class Demo { public static void main(String[] args) { Message message = new Message(); new Thread(new Producer(message)).start(); new Thread(new Consumer(message)).start(); } }
第5 章 : 多线程深入话题
20 优雅的停止线程
已废除的方法,可能会导致线程死锁,不建议使用
// 停止线程 public final void stop() // 销毁线程 public void destroy() // 挂起线程 public final void suspend() // 恢复线程 public final void resume()
使用flag 标志位不会立刻停止,而是当前线程自己判断
class Demo{ private static boolean flag = true; public static void main(String[] args) { new Thread(()->{ while (flag){ try { Thread.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在执行"); } }, "自定义线程").start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("时间到"); flag = false; } }
21 后台守护线程
守护线程,如果主线程退出,守护线程就退出
GC就是守护线程
设置为守护线程
public final void setDaemon(boolean on)
判断是否为守护线程
public final boolean isDaemon()
示例
设置线程为守护线程后,主程序执行完毕就退出了,并不会打印任何内容
class MyThread implements Runnable{ @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 正在执行"); } } class Demo{ public static void main(String[] args) { Thread t = new Thread(new MyThread()); t.setDaemon(true); t.start(); } }
22 volatile关键字
volatile 用于属性定义, 中文意思:易变的
变量处理的步骤:
(1)获取变量原有的数据内容副本
(2)利用副本为变量进行数学计算
(3)建计算后的变量,保存到原始空间中
读取read <- 数据副本 加载load 使用use 赋值asign 存储store 写入write -> 原始空间
属性上加了volatile, 没有中间拷贝过程,直接使用原始数据
区别:volatile 和 synchronized
volatile: 主要在属性上使用,无法描述同步,直接内存处理,避免副本操作
synchronized: 代码块与方法上使用
class MyThread implements Runnable{ private volatile int count = 10; @Override public void run() { while (count>0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 正在执行"); count --; } } } class Demo{ public static void main(String[] args) { Thread t = new Thread(new MyThread()); t.start(); } }