十、同步机制解决Thread继承安全问题

创建三个窗口买票,共100张票。用继承来实现

  1. 方式一:同步代码块

    public class RunMainExtends {
    public static void main(String[] args) {
    Win win1 = new Win();
    Win win2 = new Win();
    Win win3 = new Win();
    // 设置线程的名字
    win1.setName("窗口一:");
    win2.setName("窗口二:");
    win3.setName("窗口三:");
    // 开启线程
    win1.start();
    win2.start();
    win3.start();
    }
    } /* 方式一:同步代码块*/
    class Win extends Thread {
    /*
    注意:ticket、object必须加static。因为实例化的三个线程对象,是不同的对象,他们各自有自己的栈和程序计数器。只有加了static才能让这个三个对象共享ticket、object。
    */
    private static int ticket = 100;
    private static Object object = new Object();
    @Override
    public void run() {
    while (true) {
    synchronized (object){// 同步代码块,同步监视器,必须是同一把锁
    if (ticket > 0) {
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"获取到了第"+ticket+"票");
    ticket--;
    }else{
    break;
    }
    }
    }
    }
    }
  2. 方式二:同步方法

    public class RunMainExtends {
    public static void main(String[] args) {
    Win win1 = new Win();
    Win win2 = new Win();
    Win win3 = new Win();
    // 设置线程的名字
    win1.setName("窗口一:");
    win2.setName("窗口二:");
    win3.setName("窗口三:");
    // 开启线程
    win1.start();
    win2.start();
    win3.start();
    }
    } /*方式二:同步方法
    * */
    class Win extends Thread {
    private static int ticket = 100;
    private static Object object = new Object();
    @Override
    public void run() {
    while (true) {
    show();
    if (ticket == 0) {
    break;// 用于跳出循环
    }
    }
    } /* 定义一个同步方法,注意必须把这个方法定义为一静态方法
    * */
    private static synchronized void show() {
    if (ticket > 0) {
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"获取到了第"+ticket+"票");
    ticket--;
    }
    }
    }

    同步方法总结:

    /**
    * 同步机制解决“继承Thread类”的线程安问题
    * 关于同步方法的总结:
    * 1. 同步方法任然设计来到同步监视器,只是不需要我们显示的声明。
    * 2. 非静态同步方法,同步监视器是this
    * 3. 静态同步方法,同步监视器是类本身
    */

十一、线程的死锁问题

11.1 死锁简介

  1. 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。【如:有多把锁】
  2. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

11.2 死锁解决方法

  1. 专门的算法、原则

  2. 尽量减少同步资源的定义

  3. 尽量避免嵌套同步

  4. 写代码时,要尽量避免死锁

十二、方式三:Lock(锁)

12.1 Lock简介

  1. 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步同步锁使用Lock对象充当
  2. java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  3. ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

总结:Lock锁是java在5.0之后提出来的一种线程同步机制,我们可以把它 列为解决线程安全的第三种方式

12.2 列子

// 卖票
public class RunMainLock {
public static void main(String[] args) {
Wins wins = new Wins();
/*创建三个线程*/
Thread win1 = new Thread(wins);
Thread win2 = new Thread(wins);
Thread win3 = new Thread(wins);
/*设置线程名字*/
win1.setName("窗口一:");
win2.setName("窗口二:");
win3.setName("窗口三:");
/*开启线程*/
win1.start();
win2.start();
win3.start();
}
} class Wins implements Runnable{
private int ticket = 100;
/*定义Lock锁*/
/*第一步:实例化ReentrantLock*/
private ReentrantLock lock = new ReentrantLock();/* 参数为true,线程先进先出;默认为false,即cpu轮到谁就谁,看运气*/
@Override
public void run() { while (true) {
try{
/*第二步:加锁*/
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票——" + ticket);
ticket--;
} else {
break;
}
}finally {
/*第三步:解锁*/
lock.unlock();
}
}
}
}

12.3 synchronized与Lock的异同

  1. 相同点:

    1. 都能解决线程的安全问题
  2. 不同点

    1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
    2. Lock只有代码块锁,synchronized有代码块锁和方法锁
    3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  3. 总结:

    优先使用顺序(建议):

    Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

十三、线程的通信问题

13.1 初步代码

让线程执行一次之后,阻塞,把cpu资源给其他线程。

/**
* 使用两个线程打印 1-100。线程1, 线程2 交替打印
*/
public class Test {
public static void main(String[] args) {
Test1 test1 = new Test1();
Thread thread1 = new Thread(test1);
Thread thread2 = new Thread(test1);
thread1.setName("线程一:");
thread2.setName("线程二:");
thread1.start();
thread2.start();
}
}
class Test1 implements Runnable{
private int j = 1;
@Override
public void run() {
while (true) {
synchronized (this) {// this的使用,指向调用该方法的对象的引用test1。在该处做为锁
if (j <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":打印"+j);
j++;
try {
/*第一个线程打印一个数字之后,调用wait方法便阻塞了,同时释放了锁。
第二个线程获得锁之后,打印一个数字之后,调用wait方法也阻塞了,同时释放了锁。
此时这两个线程都被阻塞了,因此只打印了2次
*/
wait();
} catch (InterruptedException e) {
e.printStackTrace();
} }else{
break;
}
}
}
}
}

结果:

总结wait方法

从执行结果我们不难推断出,wait方法至少有2个作用或者功能:

  1. 阻塞当前线程
  2. 释放锁【不释放锁的的话,第二个线程怎么进得来】

13.2 最终代码

/**
* 使用两个线程打印 1-100。线程1, 线程2 交替打印
*/
public class Test {
public static void main(String[] args) {
Test1 test1 = new Test1();
Thread thread1 = new Thread(test1);
Thread thread2 = new Thread(test1);
thread1.setName("线程一");
thread2.setName("线程二");
thread1.start();
thread2.start();
}
}
class Test1 implements Runnable{
private int j = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
/*程序开始:假设此时第一个线程拿着锁(this)进来了,第二个线程在外面等待着。
第一个线程执行notify()方法没有什么影响,接着进入判断,打印了“线程一:打印1”后,调用wait()方法后阻塞,
同时释放锁(this)【这步操作很重要】。此时第二个线程拿到了锁进来了,执行notify()方法,唤醒第一个线程
【注意此时此刻,第二个线程还拿着锁,所以第一个线程被唤醒之后,也只能在外面等待】,接着第二个线程进入判断,
打印“线程二:打印2”后,调用wait()方法后阻塞,同时释放锁。此时此刻第一个线程又拿到了锁,接着执行。
【后面就是重复的了,第一个线程和第二个线程之间,不停的被阻塞、又不停的被唤醒,直到循环结束】
*
* */
//第二步
notify();
if (j <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":打印"+j);
j++;
try {
/*第一个线程打印一个数字之后,调用wait方法便阻塞了,同时释放了锁。
第二个线程获得锁之后,打印一个数字之后,调用wait方法也阻塞了,同时释放了锁。
此时这两个线程都被阻塞了,因此只打印了2次
*/
//第一步
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}

结果:实现了交替打印

总结 notify():

  1. 唤醒一个线程【因为还有notifyAll()方法】

13.3 线程通信总结

线程通信涉及到的三个方法:

  1. wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
  2. notify():一旦执行此方法,就会唤醒被wait方法的一个线程,如果有多个线程被wait,就唤醒优先级高的那个,如果优先级相同,就随机唤醒一个
  3. notifyAll():一旦执行此方法,就会唤醒被wait方法的所有线程

注意:

  1. wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。【lock中不能使用】

  2. wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。

    否则会出现IllegalMonitorStateException异常

  3. wait(),notify(),notifyAll()三个方法是定义在java.lang.object类中。

    为什么是定义在Object类中?

    • 因为你要保证“2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。”
    • 我们说过总结过任何一个对象都可以充当同步监视器,所有的对象都可以调用这三个方法,那么这三个方法必然是是定义在object类中的,因为所有的类都继承Object类。【注意Java中是单继承,但是可以多层继承。即如:Object---Parent---Child】

java 多线程-3的更多相关文章

  1. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  2. Java多线程基础知识篇

    这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...

  3. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  4. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  5. Java多线程--让主线程等待子线程执行完毕

    使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...

  6. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  7. java 多线程 1 线程 进程

    Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报  分类: javaSE综合知识点(14)  版权声明:本文为博主原创文章,未经博 ...

  8. 一起阅读《Java多线程编程核心技术》

    目录 第一章 Java多线程技能 (待续...)

  9. 第一章 Java多线程技能

    1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...

  10. java从基础知识(十)java多线程(下)

    首先介绍可见性.原子性.有序性.重排序这几个概念 原子性:即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行. 可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到 ...

随机推荐

  1. 图论算法(三) 最短路SPFA算法

    我可能要退役了…… 退役之前,写一篇和我一样悲惨的算法:SPFA 最短路算法(二)SPFA算法 Part 1:SPFA算法是什么 其实呢,SPFA算法只是在天朝大陆OIers的称呼,它的正统名字叫做: ...

  2. ybt1107题解和方法总结

    今天花了三个小时的时间刷了些基础题,虽说是简单题,但是有一些还是有点难度的 比如ybt1107,我死嗑了半个小时. [题目描述] 某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米. ...

  3. HotSpot的垃圾回收器

    如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.这里讨论的收集器基于JDK 1.7 Update 14之后的 HotSpot 虚拟机,这个虚拟机包含的所有收集器如下图所示 上图 ...

  4. 浏览器自动化的一些体会8 HttpWebRequest的几个问题

    前面说过了,httpWebRequest的好处在于轻量,不需要界面,缺点在于无法执行javascript.这里再归纳一些问题. 1. 设置代理 1) httpWebRequest不支持https的代理 ...

  5. C++置换的玩笑

    小蒜头又调皮了.这一次,姐姐的实验报告惨遭毒手. 姐姐的实验报告上原本记录着从 1 到 n 的序列,任意两个数字间用空格间隔.但是“坑姐”的蒜头居然把数字间的空格都给删掉了,整个数字序列变成一个长度为 ...

  6. CSS动画实例:太极图在旋转

    利用CSS可以构造出图形,然后可以对构造的图形添加动画效果.下面我们通过旋转的太极图.放大的五角星.跳“双人舞”的弯月等实例来体会纯CSS实现动画的方法. 1.旋转的太极图 设页面中有<div ...

  7. Vscode配置C++环境

    (终于申请博客了qaq) 之前用了那么久Dev-C++,总算换了一个编辑器,Visual Studio Code (Vscode). 界面可比以前的舒适多了. Vscode作为一款功能极其丰富的开发工 ...

  8. Goland远程连接Linux开发调试

    参考链接: https://blog.csdn.net/huwh_/article/details/77879152?utm_source=blogxgwz3 https://baijiahao.ba ...

  9. java进阶(10)--String(StringBuff、StringBuilder)

    一.基本概念 1.String为引用数据类型,使用双引号 2.字符串数组存储在方法区的内存池,因为开发过程种使用过于频繁 3.String类已经重写了equals,比较时使用,同时也重写了toStri ...

  10. 深入了解Redis(3)-对象

    Redis主要的数据结构有简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合,等等.但Redis并没有直接使用这些数据结构来实现键值对数据库, 而是基于这些数据结构创建了一个对象系统, 这个 ...