1.1、多线程基本使用

1、线程的创建方式 

多线程的创建有两种方式,分别如下:

继承

  • 继承Thread类,并重写run方法,将需要多线程的代码放入run方法中。
  • 通过Thread的子类的引用调用start()方法来开启线程。

实现

  • 定义类实现Runnable接口,覆盖Runnable接口中的run方法。
  • 通过Thread类建立线程对象。
  • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
  • 调用Thread类的star方法开启线程并调用Runnable接口子类的run方法。

继承的方式:

public class ThreadDemo {public static void main(String[] args) {new MyThread().start();}
}class MyThread extends Thread{@Overridepublic void run() {// 多线程代码System.out.println("多线程开启了");}
}

实现的方式:

public class ThreadDemo {public static void main(String[] args) {// 第一种写法
/*      MyThread t1 = new MyThread();Thread thread = new Thread(t1);thread.start();*/// 开发中写法new Thread(){@Overridepublic void run() {System.out.println("线程开启了");}}.start();}
}class MyThread implements Runnable{public void run() {System.out.println("线程开启了");}
}

线程都有自己默认的名称,通过getName和setName来进行获取和设置。同时,还可以通过Thread.currentThread()来获取当前线程对象。

2、 线程的运行状态

被创建-------------------->运行-------------------------->消亡(stop(已过时)或run方法结束)

|

阻塞-->(阻塞状态:具备运行资格,但不具备cpu执行权。)

|

冻结(sleep和wait):不具备运行资格,也不具备cpu执行权。

睡眠状态和等待状态:不具备运行资格,也没有执行权。

  • 睡眠状态:sleep(time),当线程遇到sleep会进入睡眠状态,当睡眠时间到达后可能是运行状态,也可能进入临时状态(阻塞状态)。
  • 等待状态:wait(),当线程遇到wait会进入等待状态,这时需要notify()来唤醒,唤醒后可能是运行状态,也可能是临时状态(阻塞状态)。

消亡的两种方式:通过stop()命令强行结束线程或run()方法执行结束。

3、 多线程售票实例

  火车站有100张票,分别在t1、t2、t3、t4等4个窗口进行出售。

由此首先我们考虑到的是将100张票定义为静态共享变量,并开启四个线程来出售,那么代码如下:

public class ThreadDemo {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket);// 售票窗1Thread t2 = new Thread(ticket);// 售票窗2Thread t3 = new Thread(ticket);// 售票窗3Thread t4 = new Thread(ticket);// 售票窗4
        t1.start();t2.start();t3.start();t4.start();}
}// 售票厅
class Ticket extends Thread {private static int ticks = 100;// 出售票的方法public void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {if (ticks > 0) {sale();}}}
}

输出结果:

Thread-0sale:3Thread-0sale:2Thread-0sale:1Thread-2sale:93Thread-1sale:89Thread-3sale:91

从结果中我们可以看出,售卖过程中我们发现出售的顺序已经错乱,这是因为线程太快导致,我们可以让线程在输出时进行睡眠20毫秒:

// 售票厅
class Ticket extends Thread{private static int ticks = 100;// 出售票的方法public void sale(){System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while(true){if(ticks > 0){try{Thread.sleep(20);}catch(Exception e){}sale();}        }}
}

输出结果:

Thread-2sale:77
Thread-3sale:77
Thread-0sale:77
Thread-1sale:77

从结果中,我们看到出现售卖相同的票,那么线程的安全隐患就暴露出来了,那么我们如何解决这个问题呢?

我们只能通过线程同步来解决该问题,来保住每次被并发执行的代码中只能有一个线程在操作,以此解决线程的安全问题。

4、 多线程同步

由上面代码的示例我们知道,直接开启四个线程进行售卖是存在安全隐患的,我们必须通过同步来解决线程安全问题。

同步的前提:

  • 必须要有两个或者两个以上的线程。
  • 必须是多个线程使用同一个锁。必须保证同步中只能有一个线程在运行。

线程的同步解决了多线程的安全问题,但是多个线程都需要判断锁,较为耗费资源。

a) 如果需要同步的代码只是部分,则可以使用同步代码块,同步代码块的锁对象可以是任意的对象:

// 售票厅
class Ticket extends Thread {private static int ticks = 100;Object obj = new Object();// 出售票的方法public void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {synchronized(obj){if (ticks > 0) {try{Thread.sleep(20);}catch(Exception e){}sale();}}}}
}

输出结果:

Thread-1sale:100
Thread-1sale:99
Thread-1sale:98
...
Thread-2sale:3
Thread-2sale:2
Thread-2sale:1

b) 函数需要被对象调用。那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this

// 售票厅
class Ticket extends Thread {private static int ticks = 100;Object obj = new Object();// 出售票的方法public void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {synSal();}}// 同步函数public synchronized void synSal(){if (ticks > 0) {try{Thread.sleep(20);}catch(Exception e){}sale();}}
}

输出结果:

Thread-1sale:100
Thread-1sale:99
Thread-1sale:98
...
Thread-2sale:3
Thread-2sale:2
Thread-2sale:1

c) 当同步函数被static(静态)所修饰的时候,使用的锁是所在类的字节码文件(类名.class)。

// 售票厅
class Ticket extends Thread {private static int ticks = 100;Object obj = new Object();// 出售票的方法public static void sale() {System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);}@Overridepublic void run() {while (true) {synSal();}}// 同步函数public static synchronized void synSal(){if (ticks > 0) {try{Thread.sleep(20);}catch(Exception e){}sale();}}
}

输出结果:

Thread-1sale:100
Thread-1sale:99
Thread-1sale:98
...
Thread-2sale:3
Thread-2sale:2
Thread-2sale:1

d) 多线程--死锁,同步中嵌套同步,而锁却不同就会造成死锁,从而导致程序无法继续向下运行。

class DeadLock implements Runnable{//定义一个标记private boolean flag;DeadLock(boolean flag){this.flag = flag;}//重写run方法public void run(){if(flag){synchronized(MyLock.locka){synchronized(MyLock.lockb){System.out.println("if locka");}}}else{synchronized(MyLock.lockb){synchronized(MyLock.locka){System.out.println("else lockb");}}}}
}
//定义两个锁
class MyLock{static Object locka = new Object();static Object lockb = new Object();
}
public class DeadLockDemo {public static void main(String[] args) {new Thread(new DeadLock(true)).start();new Thread(new DeadLock(false)).start();}
}

5、多线程之间的通信

a) 等待唤醒机制

  • notify():激活线程池中 wait的线程。(谁先进去就唤醒谁)
  • notifyAll():激活线程池中所有wait的线程。
  • notify,notifyAll和wait等方法必须用在同步中,也就是说同步是前提。
  • 而且这几种方法都是继承自Object类,出现异常,只能try而不能抛。
  • 等待和唤醒必须是同一个锁,锁可以是任意对象,所以方法定义在Object类中。
  • 都使用在同步中,因为要持有监视器(锁)的线程操作。
  • 所以要使用在同步中,因为只有同步才具有锁。

b) 生产者和消费者实例

  我们来看一个生产者和消费者的实例,在生产商品的同时将商品销售或消费掉。

class ProducerConsumerDemo {public static void main(String[] args) {Resource res = new Resource();      Producer pro = new Producer(res);Consumer con = new Consumer(res);Thread t1 = new Thread(pro);Thread t2 = new Thread(pro);Thread t3 = new Thread(con);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}
}class Resource {private String name;private int count = 1;private boolean flag = false;public synchronized void set(String name) {while (flag)try {this.wait();} catch (Exception e) {}this.name = name + "--" + count++;System.out.println(Thread.currentThread().getName() + "...生产者.." + this.name);flag = true;this.notifyAll();}public synchronized void out() {while (!flag)try {wait();} catch (Exception e) {}System.out.println(Thread.currentThread().getName() + "...消费者........." + this.name);flag = false;this.notifyAll();}
}

class Producer implements Runnable {private Resource res;Producer(Resource res) {this.res = res;}public void run() {while (true) {res.set("+商品+");}}
}

class Consumer implements Runnable {private Resource res;Consumer(Resource res) {this.res = res;}public void run() {while (true) {res.out();}}
}

为什么要定义while判断标记?

  对于多个生产者和消费者需要用while判断标记,来让被唤醒的线程再一次判断标记。

为什么要定义notifyAll()?

  因为在本线程进入冻结或睡眠状态时,需要唤醒对方线程,如果用notify(),容易出现只唤醒本方线程的情况,导致程序中所有线程都进入等待状态。

我们可以看下简单的总结:

  一个生产线程对应一个消费线程,我们可以用if(flag)来判断标记,唤醒必须用notify()。

if(flag)    -->    notify()

  多个生产线程对应多个消费线程,我们必须用while(flag)来判断标记,唤醒必须用notifyAll()。(while循环下唤醒是notify()的话会导致全部等待)

while(flag)    -->    notifyAll();

死锁和全部等待:死锁是争抢执行权而导致程序停止,全部等待是所有线程都进入冻结状态。

针对唤醒本方的同时也唤醒对方的问题,Jdk1.5对该问题进行了针对性的处理,请参考下面的新特性。

转载于:https://www.cnblogs.com/pengjingya/p/5529078.html

09、多线程(一) -- 基本概念相关推荐

  1. 【java多线程学习】多线程的基本概念

    今天开始系统的学习了java多线程有关的基础知识,大致先分为三个步骤:多线程的基本概念,多线程的两种使用方法(继承Thread类.实现Runable接口),线程的同步.这里先记录下下多线程的基本概念. ...

  2. 尚硅谷大数据技术Spark教程-笔记09【SparkStreaming(概念、入门、DStream入门、案例实操、总结)】

    尚硅谷大数据技术-教程-学习路线-笔记汇总表[课程资料下载] 视频地址:尚硅谷大数据Spark教程从入门到精通_哔哩哔哩_bilibili 尚硅谷大数据技术Spark教程-笔记01[SparkCore ...

  3. Java多线程——多线程的基本概念和使用

    一.进程和线程的基础知识 1.进程和线程的概念 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) 线程:进程中的一段代码,一个进程中可以有多段代码.本身不拥有资源(共享所在进程的资源) 在 ...

  4. java 线程的基本概念_Java多线程——多线程的基本概念和使用

    一.进程和线程的基础知识 1.进程和线程的概念 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) 线程:进程中的一段代码,一个进程中可以有多段代码.本身不拥有资源(共享所在进程的资源) 在 ...

  5. Java多线程上——基本概念及操作

    目录 多线程定义: 多线程编程 创建线程方法 Thread 类及常见方法 启动问题(start() 与 run()) 区别 中断线程 等待一个线程-join() 线程的状态 线程安全 线程安全定义 线 ...

  6. iOS开发 - 多线程相关的概念

    1.进程 概念 在系统中正在运行的程序 特点 进程之间相互独立,每个进程运行在自己的内存空间内 实例 同时打开QQ.迅雷,系统会启动两个不同的进程 2.线程 概念 线城是进程的基本执行单元,即进程想要 ...

  7. Java多线程-线程的概念和创建

    前言 声明:该文章中所有测试都是在JDK1.8的环境下. 该文章是我在学习Java中的多线程这方面知识时,做的一些总结和记录. 如果有不正确的地方请大家多多包涵并作出指点,谢谢! 一.基础概念 我们知 ...

  8. Java Web 实战 09 - 多线程基础之定时器

    多线程当中的定时器 定时器 1. 官方的定时器 2. 自己实现的计时器 2.1 使用优先级阻塞队列 2.2 定义比较规则 2.3 解决 CPU 空转 : wait 2.4 防止其他线程插入到入队列和线 ...

  9. 多线程(线程概念、代码示例)

    进程和线程 说起进程,就必须提一下程序,程序是指令和数据的有序集合,是一个静态的概念 进程是执行程序的一次执行过程,第一个动态的概念,是系统资源分配的单位 在一个进程中包含若干个线程,线程是独立的执行 ...

  10. [转载] 羽毛球——学打羽毛球 09 步法的基本概念

    转载于:https://www.cnblogs.com/6DAN_HUST/archive/2012/09/23/2698807.html

最新文章

  1. Linux下Boost编译安装
  2. mysql调试索引_10 分钟让你明白 MySQL 是如何利用索引的?
  3. 使用Docker迁移与备份
  4. 诊断SQLSERVER问题常用的日志
  5. 普通高中段计算机学科知识,高中计算机学科的特点及教学内容浅探
  6. C语言-结构体内存对齐
  7. 23种设计模式之门面模式
  8. request.getRequestDispatcher().forward(request,response)和response.sendRedirect()的区别
  9. Free Syslog Forwarder–免费的Syslog转发工具
  10. greenplum定期清理日志脚本-分割线后更新简单方法
  11. Redis commands 官方
  12. Git可视化工具-小乌龟
  13. 火山图——直观的特征差异可视化
  14. 2022年计算机二级考试WPS Office高级应用与设计考前冲刺题及答案
  15. 机器人搭建记录 HoshinoBot
  16. 服务器电话销售话术,电话销售必看:让客户无法拒绝的13个经典话术
  17. ThinkPHP拼团拼购h5单商户商城[可对接公众号]非常棒的一款h5拼团商城源码
  18. vim中进行复制粘贴
  19. 互联网项目团队成员及能力组成的金字塔和倒金字塔模型
  20. MATLAB教室人数统计开源代码(包含 GUI 注释 课题分析)

热门文章

  1. scrapy报错:ModuleNotFoundError
  2. nginx php 慢,Nginx+PHP-FPM时快时慢的解决
  3. vue.js2.0 新手开发_vue.js2.0实战(1):搭建开发环境及构建项目
  4. npm 编译慢_如何有效提升快应用(Webpack)编译速度
  5. refprop用matlab,Matlab 调用 REFPROP(64位)下载即可用
  6. linux用户个人的环境变量,linux下的变量以及系统和个人环境变量的配置文件
  7. 贴花纸怎么贴_地砖保护膜怎么贴—怎么贴地砖保护膜
  8. 13、Math类简介
  9. swagger-ui多端口自动切换优化
  10. Virtualbox中Ubuntu与windows共享文件夹设置