2.4创建线程的方式二
创建线程的第二种方式.使用Runnable接口.
该类中的代码就是对线程要执行的任务的定义.
1:定义了实现Runnable接口
2:重写Runnable接口中的run方法,就是将线程运行的代码放入在run方法中
3:通过Thread类建立线程对象
4:将Runnable接口的子类对象作为实际参数,传递给Thread类构造方法
5:调用Thread类的start方法开启线程,并调用Runable接口子类run方法
为什么要将Runnable接口的子类对象传递给Thread的构造函数,因为自定义的run方法所属对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法
package cn.itcast.gz.runnable;
public class Demo1 {
public static void main(String[] args) {
MyRun my = new MyRun();
Thread t1 = new Thread(my);
t1.start();
for (int i = 0; i < 200; i++) {
System.out.println(“main:” + i);
}
}
}
class MyRun implements Runnable {
public void run() {
for (int i = 0; i < 200; i++) {
System.err.println(“MyRun:” + i);
}
}
}

理解Runnable:
Thread类可以理解为一个工人,而Runnable的实现类的对象就是这个工人的工作(通过构造方法传递).Runnable接口中只有一个方法run方法,该方法中定义的事会被新线程执行的代码.当我们把Runnable的子类对象传递给Thread的构造时,实际上就是让给Thread取得run方法,就是给了Thread一项任务.

买票例子使用Runnable接口实现
在上面的代码中故意照成线程执行完后,执行Thread.sleep(100),以让cpu让给别的线程,该方法会出现非运行时异常需要处理,这里必须进行try{}catch(){},因为子类不能比父类抛出更多的异常,接口定义中没有异常,实现类也不能抛出异常。
运行发现票号出现了负数,显示了同一张票被卖了4次的情况。
出现了同样的问题。如何解决?
class MyTicket implements Runnable {
int tickets = 100;
public void run() {
while (true) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + “窗口@销售:”
+ tickets + “号票”);
tickets–;

     } else {System.out.println("票已卖完。。。");break;}}
}

}==

public class Demo6 {
public static void main(String[] args) {
MyTicket mt = new MyTicket();
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
Thread t4 = new Thread(mt);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
3锁对象
什么是锁对象?
每个java对象都有一个锁对象.而且只有一把钥匙.
如何创建锁对象:
可以使用this关键字作为锁对象,也可以使用所在类的字节码文件对应的Class对象作为锁对象

  1. 类名.class
  2. 对象.getClass()
    Java中的每个对象都有一个内置锁,
    有同步方法代码时,内置锁才会起作用,当进入一个同步的非静态方法时,就会自动获得与类的当前实例(this)相关的锁,该类的代码就是正在执行的代码。获得一个对象的锁也成为获取锁、锁定对象也可以称之为监视器来指我们正在获取的锁对象。
    因为一个对象只有一个锁,所有如果一个线程获得了这个锁,其他线程就不能获得了,直到这个线程释放(或者返回)锁。也就是说在锁释放之前,任何其他线程都不能进入同步代码(不可以进入该对象的任何同步方法)。释放锁指的是持有该锁的线程退出同步方法,此时,其他线程可以进入该对象上的同步方法。
    1:只能同步方法(代码块),不能同步变量或者类
    2:每个对象只有一个锁
    3:不必同步类中的所有方法,类可以同时具有同步方法和非同步方法
    4:如果两个线程要执行一个类中的一个同步方法,并且他们使用的是了类的同一个实例(对象)来调用方法,那么一次只有一个线程能够执行该方法,另一个线程需要等待,直到第一个线程完成方法调用,总结就是:一个线程获得了对象的锁,其他线程不可以进入该对象的同步方法。
    5:如果类同时具有同步方法和非同步方法,那么多个线程仍然可以访问该类的非同步方法。
    同步会影响性能(甚至死锁),优先考虑同步代码块。
    6:如果线程进入sleep() 睡眠状态,该线程会继续持有锁,不会释放。
    4死锁

经典的“哲学家就餐问题”,5个哲学家吃中餐,坐在圆卓子旁。每人有5根筷子(不是5双),每两个人中间放一根,哲学家时而思考,时而进餐。每个人都需要一双筷子才能吃到东西,吃完后将筷子放回原处继续思考,如果每个人都立刻抓住自己左边的筷子,然后等待右边的筷子空出来,同时又不放下已经拿到的筷子,这样每个人都无法得到1双筷子,无法吃饭都会饿死,这种情况就会产生死锁:每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
1:两个任务以相反的顺序申请两个锁,死锁就可能出现
2:线程T1获得锁L1,线程T2获得锁L2,然后T1申请获得锁L2,同时T2申请获得锁L1,此时两个线程将要永久阻塞,死锁出现
如果一个类可能发生死锁,那么并不意味着每次都会发生死锁,只是表示有可能。要避免程序中出现死锁。
例如,某个程序需要访问两个文件,当进程中的两个线程分别各锁住了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。
3:要避免死锁
public class DeadLock {
public static void main(String[] args) {
new Thread(new Runnable() { // 创建线程, 代表中国人
public void run() {
synchronized (“刀叉”) { // 中国人拿到了刀叉
System.out.println(Thread.currentThread().getName()
+ “: 你不给我筷子, 我就不给你刀叉”);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (“筷子”) {
System.out.println(Thread.currentThread()
.getName() + “: 给你刀叉”);
}
}
}
}, “中国人”).start();
new Thread(new Runnable() { // 美国人
public void run() {
synchronized (“筷子”) { // 美国人拿到了筷子
System.out.println(Thread.currentThread().getName()
+ “: 你先给我刀叉, 我再给你筷子”);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (“刀叉”) {
System.out.println(Thread.currentThread()
.getName() + “: 好吧, 把筷子给你.”);
}
}
}
}, “美国人”).start();
}
}
5线程的通讯
线程间通信其实就是多个线程在操作同一个资源,但操作动作不同
生产者消费者
如果有多个生产者和消费者,一定要使用while循环判断标记,然后在使用notifyAll唤醒,否者容易只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。
例如:有一个数据存储空间,划分为两个部分,一部分存储人的姓名,一部分存储性别,我们开启一个线程,不停地想其中存储姓名和性别(生产者),开启另一个线程从数据存储空间中取出数据(消费者)。
由于是多线程的,就需要考虑,假如生产者刚向数据存储空间中添加了一个人名,还没有来得及添加性别,cpu就切换到了消费者的线程,消费者就会将这个人的姓名和上一个人的性别进行了输出。
还有一种情况是生产者生产了若干次数据,消费者才开始取数据,或者消费者取出数据后,没有等到消费者放入新的数据,消费者又重复的取出自己已经去过的数据。
public class Demo10 {
public static void main(String[] args) {
Person p = new Person();
Producer pro = new Producer§;
Consumer con = new Consumer§;
Thread t1 = new Thread(pro, “生产者”);
Thread t2 = new Thread(con, “消费者”);
t1.start();
t2.start();
}
}

// 使用Person作为数据存储空间
class Person {
String name;
String gender;
}

// 生产者
class Producer implements Runnable {
Person p;

public Producer() {}public Producer(Person p) {this.p = p;
}@Override
public void run() {int i = 0;while (true) {if (i % 2 == 0) {p.name = "jack";p.gender = "man";} else {p.name = "小丽";p.gender = "女";}i++;}}

}

// 消费者
class Consumer implements Runnable {
Person p;

public Consumer() {}public Consumer(Person p) {this.p = p;
}@Override
public void run() {while (true) {System.out.println("name:" + p.name + "---gnder:" + p.gender);}
}

}

在上述代码中,Producer和Consumer 类的内部都维护了一个Person类型的p成员变量,通过构造函数进行赋值,在man方法中创建了一个Person对象,将其同时传递给Producer和Consumer对象,所以Producer和Consumer访问的是同一个Person对象。并启动了两个线程。

输出:

显然屏幕输出了小丽 man 这样的结果是出现了线程安全问题。所以需要使用synchronized来解决该问题。

package cn.itcast.gz.runnable;

public class Demo10 {
public static void main(String[] args) {
Person p = new Person();
Producer pro = new Producer§;
Consumer con = new Consumer§;
Thread t1 = new Thread(pro, “生产者”);
Thread t2 = new Thread(con, “消费者”);
t1.start();
t2.start();
}
}

// 使用Person作为数据存储空间
class Person {
String name;
String gender;
}

// 生产者
class Producer implements Runnable {
Person p;

public Producer() {}public Producer(Person p) {this.p = p;
}@Override
public void run() {int i = 0;while (true) {synchronized (p) {if (i % 2 == 0) {p.name = "jack";p.gender = "man";} else {p.name = "小丽";p.gender = "女";}i++;}}}

}

// 消费者
class Consumer implements Runnable {
Person p;

public Consumer() {}public Consumer(Person p) {this.p = p;
}@Override
public void run() {while (true) {synchronized (p) {System.out.println("name:" + p.name + "---gnder:" + p.gender);}}
}

}

编译运行:屏幕没有再输出jack –女 或者小丽- man 这种情况了。说明我们解决了线程同步问题,但是仔细观察,生产者生产了若干次数据,消费者才开始取数据,或者消费者取出数据后,没有等到消费者放入新的数据,消费者又重复的取出自己已经去过的数据。这个问题依然存在。
升级:在Person类中添加两个方法,set和read方法并设置为synchronized的,让生产者和消费者调用这两个方法。
public class Demo10 {
public static void main(String[] args) {
Person p = new Person();
Producer pro = new Producer§;
Consumer con = new Consumer§;
Thread t1 = new Thread(pro, “生产者”);
Thread t2 = new Thread(con, “消费者”);
t1.start();
t2.start();
}
}

// 使用Person作为数据存储空间
class Person {
String name;
String gender;

public synchronized void set(String name, String gender) {this.name = name;this.gender = gender;
}public synchronized void read() {System.out.println("name:" + this.name + "----gender:" + this.gender);
}

}

// 生产者
class Producer implements Runnable {
Person p;

public Producer() {}public Producer(Person p) {this.p = p;
}@Override
public void run() {int i = 0;while (true) {if (i % 2 == 0) {p.set("jack", "man");} else {p.set("小丽", "女");}i++;}}

}

// 消费者
class Consumer implements Runnable {
Person p;

public Consumer() {}public Consumer(Person p) {this.p = p;
}@Override
public void run() {while (true) {p.read();}
}

}

需求:我们需要生产者生产一次,消费者就消费一次。然后这样有序的循环。
这就需要使用线程间的通信了。Java通过Object类的wait,notify,notifyAll这几个方法实现线程间的通信。
1.1.1.等待唤醒机制
wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。
notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。
notifyAll:唤醒持有同一监视器中调用wait的所有的线程。

如何解决生产者和消费者的问题?
可以通过设置一个标记,表示数据的(存储空间的状态)例如,当消费者读取了(消费了一次)一次数据之后可以将标记改为false,当生产者生产了一个数据,将标记改为true。
,也就是只有标记为true的时候,消费者才能取走数据,标记为false时候生产者才生产数据。
代码实现:
package cn.itcast.gz.runnable;

public class Demo10 {
public static void main(String[] args) {
Person p = new Person();
Producer pro = new Producer§;
Consumer con = new Consumer§;
Thread t1 = new Thread(pro, “生产者”);
Thread t2 = new Thread(con, “消费者”);
t1.start();
t2.start();
}
}

// 使用Person作为数据存储空间
class Person {
String name;
String gender;
boolean flag = false;

public synchronized void set(String name, String gender) {if (flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}this.name = name;this.gender = gender;flag = true;notify();
}public synchronized void read() {if (!flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("name:" + this.name + "----gender:" + this.gender);flag = false;notify();
}

}

// 生产者
class Producer implements Runnable {
Person p;

public Producer() {}public Producer(Person p) {this.p = p;
}@Override
public void run() {int i = 0;while (true) {if (i % 2 == 0) {p.set("jack", "man");} else {p.set("小丽", "女");}i++;}}

}

// 消费者
class Consumer implements Runnable {
Person p;

public Consumer() {}public Consumer(Person p) {this.p = p;
}@Override
public void run() {while (true) {p.read();}
}

}

线程间通信其实就是多个线程在操作同一个资源,但操作动作不同,wait,notify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
为什么这些方法定义在Object类中
因为这些方法在操作线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被统一锁上notify唤醒,不可以对不同锁中的线程进行唤醒,就是等待和唤醒必须是同一个锁。而锁由于可以使任意对象,所以可以被任意对象调用的方法定义在Object类中
wait() 和 sleep()有什么区别?
wait():释放资源,释放锁。是Object的方法
sleep():释放资源,不释放锁。是Thread的方法
定义了notify为什么还要定义notifyAll,因为只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。

Runnable方式创建线程详解相关推荐

  1. python实现单例模式的几种方式_基于Python中单例模式的几种实现方式及优化详解...

    单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...

  2. 论vue3.0和vue2.0区别之编程方式及例子详解

    系列文章目录 第一章 论vue3.0和vue2.0区别之编程方式及例子详解 第二章 同一台电脑 实现 vue-cli2和vue-cli3同时并存 及 常见命令 第三章 vue3.0项目实战 - Ele ...

  3. android任务 进程 线程详解,Android任务、进程、线程详解

    singleTop模式,基本上于standard分歧,仅正在请求的Activity反好位于栈顶时,无所区别.此时,配放成singleTop的Activity,不再会构制新的实例加入到Task栈外,而是 ...

  4. python与golang_Golang与python线程详解及简单实例

    Golang与python线程详解及简单实例 在GO中,开启15个线程,每个线程把全局变量遍历增加100000次,因此预测结果是 15*100000=1500000. var sum int var ...

  5. 通用线程:POSIX 线程详解,第 2部分——称作互斥对象的小玩意

    通用线程:POSIX 线程详解,第 2部分--称作互斥对象的小玩意 Daniel Robbins (drobbins@gentoo.org), 总裁/CEO, Gentoo Technologies, ...

  6. Java并发包——使用新的方式创建线程

    Java并发包--使用新的方式创建线程 摘要:本文主要学习了如何使用Java并发包中的类创建线程. 部分内容来自以下博客: https://www.cnblogs.com/dolphin0520/p/ ...

  7. Python线程详解

    Python线程详解 线程简介 开启多线程 线程之间共享 GIL全局解释器锁 线程间通信 线程简介 线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元. ...

  8. 个人Internet网站创建过程详解

    个人Internet网站创建过程详解 本文通过创建Internet示范网站--自由网络(Webfree)的实例,详细叙述了Windows NT安装.WWW服务.FTP服务的配置.邮件服务.新闻讨论组服 ...

  9. 使用Runnable接口创建线程,很简单

    大家好,今天分享.使用Runnable接口创建线程 首先Java创建线程可以通过三种方法: 即: 1.继承Thread类创建线程类(重点) 2.通过Runnable接口创建线程类(重点) 3.通过Ca ...

最新文章

  1. 栈和托管堆/值类型和引用类型/强制类型转换/装箱和拆箱[C#]
  2. TBox Library
  3. 使用 Node.js 开发简单的脚手架工具
  4. al-khaser 调试器,虚拟机检测工具
  5. Linux系统编程2:基础篇之详解Linux中的权限问题
  6. django model中的DateField()转为时间戳
  7. Java折叠_[Java教程]Jquery中菜单的展开和折叠
  8. 【关系抽取】从头来看关系抽取-远程监督来袭
  9. MAC终端命令自动补全
  10. matlab画折现_用matlab画折线图
  11. 用计算机制作标准曲线的方法,如何制作标准曲线
  12. 币图告诉你如何解决双花问题
  13. 苹果手机计算机歌曲谱,iPhone技巧篇 手机音乐如何导出至电脑
  14. 使用ONNXRuntime部署阿里达摩院开源DAMO-YOLO目标检测,一共包含27个onnx模型(代码开源)...
  15. python:机器学习(sklearn)(一)
  16. 三维计算机动画的特征是真实性,三维动画技术有哪些优势特征呢?
  17. CHECK约束使用自定义函数
  18. 【SpringCloud】设置接口同时支持返回多种数据类型(json、xml)
  19. Webpack | webpack配置eslint
  20. 电动汽车驱动防滑控制系统设计

热门文章

  1. 影之刃服务器维护,《影之刃3》服务器卡顿问题解决方法
  2. 奥数 python_从小学奥数题到Python
  3. window server2008部署winform 报错CLR20r3
  4. Stackoverflow热门问题(二十二)-如何在Windows cmd中得到程序返回值?
  5. oracle NBA连续夺冠的题目
  6. [精华分享][独立游戏][Steam][发行商]独立游戏发行商发行平台
  7. 用 Gob 传输数据
  8. 四川星利恒:拼多多想退运费要怎么做
  9. 《Windows 大排档》目录
  10. 反汇编软件IDA V7.2打开文件时python.dll error: 找不到指定的模块