Java中的多线程

进程(process)是程序的一次执行过程,或是正在运行的有一个程序,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期。

线程(thread),进程可进一步细化线程是一个程序内部的一个执行路径

  • 若一个进程用以时间并行执行多个线程,就是支持多线程
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序技术器(PC),线程切换开销小
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就睡带来安全隐患。

并行与并发

  • 并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀。多个人做同一件事。

多线程的优点:

  • 提高应用程序的响应。对图形化页面更有意义,可增强用户体验
  • 提高计算机系统CPU的利用率
  • 改善程序结果,将既长又复杂的的进程分为多线程,独立运行,利于理解和改善

多线程的创建

方式一:继承与Thread类

  1. 创建一个继承与Thread类的子类
  2. 重新Thread了的run()
  3. 创建Thread类的子类的独享
  4. 通过此对象调用start():(1)启动当前线程(2)调用当前线程的run()

线程中常用的方法解析

package com.haust.java;
/*测试Thread中的常用方法:
* 1、start():启动当前线程,调用当前线程的run()
* 2、run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3、currentThread():静态方法,返回执行当前代码的线程
* 4、getName():获取当前线程的名字
* 5、setName():设置当前线程的名字
* 6、yield():释放当前CPU的执行权
* 7、join():在线程A中,调用线程B的join方法,此时线程A就进入阻塞状态,直至线程B完全执行,线程A才继续执行
* 8、stop():已过时,当执行此方法时,强制结束当前线程
* 9、sleep(long millitime):让当前线程休眠一段指定的时间,参数的单位是毫秒;1000毫秒 = 1秒,在指定的时间内,当前线程是阻塞状态
* 10、isAlive():判断当前线程是否还存活*/
public class ThreadMethodTest {public static void main(String[] args) {HelloThread h1 = new HelloThread("线程1:");
//        h1.setName("线程一:");h1.start();//给主线程命名:Thread.currentThread().setName("主线程:");for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName()+":" +i);}if(i==20){try {h1.join();} catch (InterruptedException e) {e.printStackTrace();}}}}
}
class HelloThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+":" +i);}if(i%20==0){yield();}}}public HelloThread(String name){super(name);}
}

线程的调度

  • 时间片
  • 抢占式:高优先级的线程抢占CPU

Java的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略

线程的优先级

MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5

涉及的方法

  • getPriority():返回线程优先值
  • setPriority(int newPriority):改变线程的优先级

说明:

  • 线程创建时继承父线程的优先级
  • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

卖票案例(存在线程安全问题)

package com.haust.java;
/*例子:创建三个窗口卖票,总票数为100张
* 存在线程安全问题,待解决*/
class Window extends Thread{private static int ticket = 100;@Overridepublic void run() {while(true){if(ticket > 0){System.out.println(getName() + "卖票,票号为:"+ticket);ticket--;}else{break;}}}
}
public class WindowTest {public static void main(String[] args) {Window t1 = new Window();Window t2 = new Window();Window t3 = new Window();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}

方式二:实现Runnable接口

package com.haust.java;
/*创建多线程的方式2:实现Runnable接口
* 1、创建实现了Runnable接口的类
* 2、实现类去实现Runnable中的抽象方法:run()
* 3、创建实现类的对象
* 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5、通过Thread类的对象调用start()*/
public class ThreadTest1 {public static void main(String[] args) {
//        3、创建实现类的对象MThread mThread = new MThread();
//        4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象Thread thread = new Thread(mThread);
//        5、通过Thread类的对象调用start()①启动线程②调用当前线程内的run()方法--->调用了Runnable类型的target的run()thread.setName("线程1:");thread.start();
//      再启动一个线程,遍历100以内的偶数Thread thread1 = new Thread(mThread);thread1.setName("线程2:");thread1.start();}
}
//1、创建实现了Runnable接口的类
class MThread implements Runnable{
//2、实现类去实现Runnable中的抽象方法:run()@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName()+ ":" + i);}}}
}

比较创建线程的两种方式

开发中优先选择实现Runnable方式

原因:

  1. 实现的方式没有类的单继承性的局限性
  2. 实现的方式更适合来处理多个线程共享数据的情况

相同点:都需要重写run(),将线程要执行的代码声明在run()方法中

线程的生命周期

解决线程安全问题;

使用同步代码块

package com.haust.java;
/*出现线程安全问他的 解决方法:
* 当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程参与进来,也操作完ticket时,线程才可以开始操作ticket,这种情况即使线程a出现了阻塞,也不能被改变*/
/*在Java中,我们通过同步机制,来解决线程的安全问题
*
* 方式1:同步代码块
*synchronized(同步监视器){//需要被同步的代码}
* 说明:操作共享数据的代码,即为需要被同步的代码
* 共享数据:多个线程共同操作的变量,比如:ticket就是共享数据
* 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁.
* 要求:多个线程必须要共用同一把锁。
*
* 方式2:同步方法
*
* 同步的方式解决了线程安全问题——好处
* 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
* */
public class WindowTest1 {public static void main(String[] args) {Window1 w = new Window1();Thread thread1 = new Thread(w);thread1.setName("窗口1");Thread thread2= new Thread(w);thread2.setName("窗口2");thread1.start();thread2.start();}
}class Window1 implements Runnable{private int ticket = 100;Object obj = new Object();@Overridepublic void run() {while(true){synchronized (obj){if(ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "票号:" + ticket);ticket--;}else{break;}}}}
}

使用同步方法

线程的死锁问题

死锁

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

解决方法

  • 专门的算法、原则
  • 进来减少同步资源的定义
  • 进来避免嵌套同步

解决线程安全问题方式三:

package com.haust.java1;import java.util.concurrent.locks.ReentrantLock;/*解决线程安全问题的方式三:Lock锁——JDK5.0新增
*
* 面试题:synchronized与Lock的异同点
*   相同点:二者都可以解决线程安全问题
*   不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
*           Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
* 面试题:如何解决线程安全问题 */
public class LockTest {public static void main(String[] args) {Window w = new Window();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
class Window implements Runnable{private int ticket = 100;//1、实例化ReentrantLockprivate ReentrantLock lock = new ReentrantLock(true);@Overridepublic void run() {while(true){try {//2、调用Lock方法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 {//3、调用解锁方法:unlock()lock.unlock();}}}
}

存钱案例

package com.haust.exer;
/*银行有一个账户
* 有两个储户分别想同一个账户存3000元,每次存1000,存3次,每次存完打印账户余额
*
* 分析:
* 1、是否是多线程问题?是,两个储户线程
* 2、是否有共享数据?有,账户(或账户余额)
* 3、是否有线程安全问题?有
* 4、是否需要考虑如何解决线程安全问题?同步机制:有三种方式
* */class Account{private double balance;public Account(double balance) {this.balance = balance;}//存储public synchronized void deposit(double amt){if (amt > 0){balance += amt;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"存钱成功,账户余额为:" + balance);}}
}
class Customer extends Thread{private Account account;public Customer(Account account){this.account = account;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {account.deposit(1000);}}
}
public class AccountTest {public static void main(String[] args) {Account account = new Account(0);Customer c1 = new Customer(account);Customer c2 = new Customer(account);c1.setName("甲");c2.setName("乙");c1.start();c2.start();}
}

线程通信的例子

package com.haust.java2;
/*线程通信的例子:使用两个线程打印1-100,线程A,线程B,交替打印
* 涉及到的三个方法:
* wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
* notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
*
* 说明:
* 1、wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中
* 2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常*/
class Number implements Runnable{private int number = 1;@Overridepublic void run() {while (true){synchronized (this){notify();if(number <= 100){System.out.println(Thread.currentThread().getName()+":"+number);number++;try {wait();//使得调用如下wait()方法的进程进入阻塞状态} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}
}
public class CommunicationTest {public static void main(String[] args) {Number number = new Number();Thread t1 = new Thread(number);Thread t2 = new Thread(number);t1.setName("A");t2.setName("B");t1.start();t2.start();}
}

面试题:sleep()方法和wait()方法的异同

相同点:

  • 一旦执行方法,都可以使得当前的线程进入阻塞状态

不同点:

  • 两个方法声明的位置不同

    • Thread类中声明sleep(),Object类中声明wait()
  • 调用的要求不同:
    • sleep()可以用在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
  • 关于是否释放同步监视器
    • 如果两个方法都是用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁

经典消费者与生产者案例

package com.haust.java2;
/*线程通信的应用
*
* 分析:
* 1、是否有多线程问题?是,生产者线程,消费者线程
* 2、是否有共享数据?是店员
* 3、如何解决线程的安全问题?同步机制三种方法
* 4、是否涉及到线程的通信?是
*
* */
class  Clerk{private int productCount = 0;
//生产产品public synchronized void produceProduct() {if(productCount < 20){productCount++;System.out.println(Thread.currentThread().getName()+"开始生产第"+productCount+"个产品");notify();}else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}
//消费产品public synchronized void consumeProduct() {if (productCount > 0){System.out.println(Thread.currentThread().getName()+"开始销售第"+productCount+"个产品");productCount--;notify();}else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}class Producer extends Thread{private Clerk clerk;public Producer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {System.out.println(getName()+":开始生产产品……");while(true){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}clerk.produceProduct();}}
}
class Consumer extends Thread{private Clerk clerk;public Consumer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {System.out.println(getName()+":开始购买产品……");while(true){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}clerk.consumeProduct();}}
}
public class ProductTest {public static void main(String[] args) {Clerk c = new Clerk();Producer p1 = new Producer(c);p1.setName("生产者1");Consumer c1 = new Consumer(c);c1.setName("消费者1");p1.start();c1.start();}
}

JDK5.0新增线程创建方式

新增方式一:实现Callable接口

与使用Runnable相比,Callable功能更强大一下

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持范型的返回值
  • 需要借助FutureTask类,比如获取返回结果

Future接口

  • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
  • FutureTask是Future接口的唯一实现类
  • FutureTask同时实现了Runnable,Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
package com.haust.java2;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*创建线程的方式三:实现Callable接口。JDK5.0新增
* */
//1、创建一个实现Callable的实现类
class NumThread implements Callable{//    2、实现call()方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}
public class ThreadNew {public static void main(String[] args) {//        3、创建callable接口实现类的对象NumThread numThread = new NumThread();
//        4、将此callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask<NumThread> futureTask = new FutureTask<NumThread>(numThread);Thread thread = new Thread(futureTask);thread.start();try {//get()返回值即为FutureTask构造参数Callable实现类重写的call()的返回值Object sum = futureTask.get();System.out.println("总和为:"+sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}

创建多线程的方式四

package com.haust.java2;import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*创建多线程的方式四*/
public class ThreadPool {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(10);service.execute(new NumThread1());//适合适用于Runnableservice.execute(new NumThread2());//适合适用于Runnable
//        service.submit(Callable callable);//适合用于Callable}
}
class NumThread1 implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100 ; i++) {if (i %2 == 0) {System.out.println(Thread.currentThread().getName()+":"+i);}}}
}
class NumThread2 implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100 ; i++) {if (i %2 != 0) {System.out.println(Thread.currentThread().getName()+":"+i);}}}
}

使用线程池的好处

  • 提升响应速度(减少了创建新线程的时间)

  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

  • 便于线程管理
    / service.submit(Callable callable);//适合用于Callable
    }
    }
    class NumThread1 implements Runnable{
    @Override
    public void run() {
    for (int i = 0; i <= 100 ; i++) {
    if (i %2 == 0) {
    System.out.println(Thread.currentThread().getName()+":"+i);

          }}
    

    }
    }
    class NumThread2 implements Runnable{
    @Override
    public void run() {
    for (int i = 0; i <= 100 ; i++) {
    if (i %2 != 0) {
    System.out.println(Thread.currentThread().getName()+":"+i);

          }}
    

    }
    }


## 使用线程池的好处- 提升响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理

Java基础——深入理解Java中的多线程(超级详细,值得你看)相关推荐

  1. Java基础——深入理解Java线程池

    简介 我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因 ...

  2. java里面value_「Java基础知识」Java中包含哪些运算符

    原标题:「Java基础知识」Java中包含哪些运算符 在Java中包含的运算符有:算数运算符,逻辑运算符,关系运算符等. 算数运算符也就是我们平时的加减乘除余等操作:在Java中都是将右边的值赋值给左 ...

  3. java里面string什么意思_「Java基础知识」Java中的字符串是什么

    原标题:「Java基础知识」Java中的字符串是什么 字符串顾名思义就是一些字符组合在一起组成的一串数据,称作字符串,在Java中字符串用双引号包围起来,格式为String string = &quo ...

  4. Java 位运算理解 Java中的位移运算整理 Java右移n位 Java左移n位

    Java 位运算理解 Java中的位移运算整理 Java右移n位 Java左移n位 一.概述 1.在浏览一篇文章时,看到一个介绍 ,使用位移操作替代乘除法 ,若位移多位该怎么计算呢?  二.代码理解 ...

  5. 打怪升级之小白的大数据之旅(一)<Java基础语法之Java的身世之谜>

    打怪升级之小白的大数据之旅(一) Java基础语法之Java的身世之谜 打怪升级之小白的大数据之旅(一) 前言 一.学习大数据之前 二.Java基础 what? why? how? 总结 前言 做了几 ...

  6. 视频教程-Java基础与实践-Java

    Java基础与实践 CSDN高校俱乐部指导老师,程序爱好者,教师,国家认证的软件架构设计师.系统分析师.信息系统项目管理师.软件设计师.网络工程师.本人热衷于计算机软件相关的研发.技术探讨.学习分享和 ...

  7. Java基础2019最新Java面试经典题解析

    Java基础2019最新Java面试经典题解析 1简述JVM.JRE.JDK的区别 JVM:java虚拟机 ,加载.class并运行.class JRE:java运行环境除了包含JVM以外还包含了运行 ...

  8. 《Java基础知识》Java变量的声明、初始化和作用域

    <Java基础知识>Java变量的声明.初始化和作用域 一.Java变量的声明 在 Java 程序设计中,每个声明的变量都必须分配一个类型.声明一个变量时,应该先声明变量的类型,随后再声明 ...

  9. c语言捕鱼达人源码,用捕鱼达人去理解C中的多线程.doc

    用<捕鱼达人>去理解C 中的多线程 线程是进程中某个单一顺序的控制流,是程序运行中的调度单位,是程序执行流的最小单位,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成. ...

最新文章

  1. 4层板到12层板叠层经典设计方案
  2. CornerNet代码解析——损失函数
  3. JavaScript之Promise实现
  4. vue.js仿饿了么app---webpack配置项
  5. jquery压缩图片插件
  6. mysql使用date_format格式化日期
  7. Reversing Ethereum Smart Contracts: Part 2
  8. linux下添加,删除,修改,查看用户和用户组
  9. linux之scp命令
  10. python简介及环境安装
  11. java 1.8签名apk_给Android的APK程序签名和重新签名的方法
  12. 小程序设置页面背景颜色
  13. DOS命令大全(经典收藏)【运行CMD后的命令】
  14. 从百度有啊独立看O2O发展的三大趋势
  15. 中国市场开疆辟土,TokenRank与BiYong达成战略合作
  16. 什么是需求预测(Forecasting: Principles and practice第一章)
  17. Python带你了解数据结构【二】
  18. icloud 照片导出_我的照片流和iCloud照片之间有什么区别?
  19. Java能抵挡住JavaScript的进攻吗?【转载】
  20. 如何提高情商?情商书籍推荐

热门文章

  1. litesql mysql 使用_Android使用sqllite实例
  2. obs多推流地址_如何用一台笔记本实现直播推流功能
  3. Chrome浏览器网页静音快捷键
  4. java支持库(_Java运行环境支持库下载-Java Runtime Environment(JRE) v9.1.8官方64位版-ucbug下载站...
  5. 内存卡格式化数据恢复方法
  6. 数字电路和模拟电路-8触发器
  7. SSO单点登录系统的实战运用
  8. Struts2、Mybatis、Spring整合
  9. qt 判断是否为整型_第三节:整型数据(整数)
  10. 生产制造业MES管理系统,源码分享