1进程和线程

进程:一个进程就是一个执行中的程序。每一个进程都有自己独立的一块内存空间,一组系统资源。

线程:线程就是进程中的一个负责程序执行的控制单元(执行路径)。同类的多个线程是共享一块内存空间和一组系统资源。所以系统在各个线程之间切换时,开销要比进程小得多,正因如此,线程被称为轻量级进程。一个进程中可以包含多个线程。

Java程序至少会有一个线程,这就是主线程,程序启动后由JVM创建主线程,程序结束时由JVM停止主线程。主线程负责管理子线程,即子线程的启动,挂起,停止等操作。

获取主线程示例代码如下:

package duoxiancheng;
public class HelloThread {public static void main(String[] args) {//获取主线程Thread mainThread=Thread.currentThread();System.out.println("主线程名:"+mainThread.getName());}
}

Thread.currentThread()可以获得当前线程对象,getName()是Thread类的实例方法,可以获得线程的名字。

2创建子线程

Java中创建一个子线程涉及java.lang.Thread类java.lang.Runnable接口

Thread是线程类,创建一个Thread对象就会产生一个新的线程。

实现Runnable接口对象是线程执行对象,需要实现run()方法。子线程处理代码放到run()方法中,run()方法称为线程体。

有两种方式可以创建子线程:

①实现Runnable接口,实现run()方法。

②继承Thread类,重写run()方法。

2.1实现Runnable接口

创建线程Thread对象时,可以将线程执行对象传递给它,这需要用到Thread类的两个构造方法:

Thread(Runnable target):target是线程执行对象,实现Runnable接口。线程名字是由JVM分配的。

Thread(Runnable target, String name) :target是线程执行对象,实现Runnable接口。name是线程名字。

下面看一个具体示例。实现Runnable接口的线程执行对象Runner代码如下:

package duoxiancheng;
//线程执行对象
public class Runner implements Runnable {//编写执行线程代码@Overridepublic void run() {for(int i=1;i<=10;i++){//打印次数和线程的名字System.out.printf("第%d次执行 - %s\n",i,Thread.currentThread().getName());//随机生成休眠时间try {long sleepTime=(long)(1000*Math.random());Thread.sleep(sleepTime);} catch (InterruptedException e) {e.printStackTrace();}}//线程执行结束System.out.println("执行完成!"+Thread.currentThread().getName());}
}

代码Thread.sleep(sleepTime)是休眠当前线程,sleep是静态方法。它有两个版本:

static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

static void sleep(long millis, int nanos):导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行)。

测试程序HelloThread代码如下:

package duoxiancheng;
public class HelloThread {public static void main(String[] args) {//创建线程t1,参数是一个线程执行对象RunnerThread t1=new Thread(new Runner());//开始线程t1t1.start();//创建线程t2,参数是一个线程执行对象RunnerThread t2=new Thread(new Runner(),"MyThread");//开始线程t2t2.start();}
}

线程创建完成还需要调用start()方法才能执行,start()方法一旦调用,线程进入可以执行状态,可以执行状态下的线程等待CPU调度执行,CPU调度后线程进入执行状态,运行run()方法。

运行结果:

2.2继承Thread线程类

事实上,Thread类也实现了Runnable接口,所以Thread类也可以作为线程执行对象,这需要继承Thread类覆盖run()方法。

采用继承Thread类重新实现2.1节示例。自定义线程类MyThread代码如下:

package duoxiancheng;
//线程执行对象
public class MyThread extends Thread {public MyThread(){super();}public MyThread(String name){super(name);}//编写执行线程代码@Overridepublic void run() {// TODO Auto-generated method stubfor(int i=1;i<=10;i++){//打印次数和线程的名字System.out.printf("第%d次执行 - %s\n",i,Thread.currentThread().getName());//随机生成休眠时间try {long sleepTime=(long)(1000*Math.random());Thread.sleep(sleepTime);} catch (InterruptedException e) {e.printStackTrace();}}//线程执行结束System.out.println("执行完成!"+Thread.currentThread().getName());}
}

测试程序HelloThread代码如下:

package duoxiancheng;
public class HelloThread {public static void main(String[] args) {//创建线程t1Thread t1=new MyThread();//开始线程t1t1.start();//创建线程t2Thread t2=new MyThread("MyThread");//开始线程t2t2.start();}
}

2.3使用匿名内部类和Lambda表达式实现线程体

如果线程体使用的地方不是很多,可以不用单独定义一个类。可以使用匿名内部类或Lambda表达式实现Runnable接口。

函数式接口,匿名内部类可以使用lamda表达式。

重新实现2.1节示例.代码如下:

package duoxiancheng;
public class HelloThread {public static void main(String[] args) {//创建线程t1,参数是实现Runnable接口的匿名内部类Thread t1=new Thread(new Runnable() {       //①//编写执行线程代码@Overridepublic void run() {for(int i=1;i<=10;i++){//打印次数和线程的名字System.out.printf("第%d次执行 - %s\n",i,Thread.currentThread().getName());//随机生成休眠时间try {long sleepTime=(long)(1000*Math.random());Thread.sleep(sleepTime);} catch (InterruptedException e) {e.printStackTrace();}}//线程执行结束System.out.println("执行完成!"+Thread.currentThread().getName());}});//开始线程t1t1.start();//创建线程t2,参数是实现Runnable接口的Lambda表达式Thread t2=new Thread(()->{     //②for(int i=1;i<=10;i++){//打印次数和线程的名字System.out.printf("第%d次执行 - %s\n",i,Thread.currentThread().getName());//随机生成休眠时间try {long sleepTime=(long)(1000*Math.random());Thread.sleep(sleepTime);} catch (InterruptedException e) {e.printStackTrace();}}//线程执行结束System.out.println("执行完成!"+Thread.currentThread().getName());},"MyThread");//开始线程t2t2.start();}
}

上述代码第①行采用匿名内部类实现Runnable接口,覆盖run()方法。这里使用的是Thread(Runnable target)构造方法。代码第②行采用Lambda表达式实现Runnable接口,覆盖run()方法。这里使用的是Thread(Runnable target, String name)构造方法,Lambda表达式是它的第一个参数。

3线程状态

线程从创建、运行到结束总是处于下面五个状态之一:新建状态就绪状态运行状态阻塞状态死亡状态

1.新建状态

新建状态(new)是用new操作符创建一个线程时。此时程序还没有开始运行线程中的代码,它仅仅是一个空的线程对象。

2.就绪状态

当主线程调用新线程的start()方法后,它就进入就绪状态(Runnable)。此时的线程尚未真正开始执行run()方法,它必须等待CPU的调度。

3.运行状态

CPU调度就绪状态的线程,线程进入运行状态(running),处于运行状态的线程独占CPU,执行run()方法。

4.阻塞状态

因为某种原因进入运行状态的线程会进入不可运行状态,即阻塞状态(blocked),处于阻塞状态的线程JVM系统不能执行,即使CPU空闲,也不能执行该线程。线程运行过程中,可能由于各种原因进入阻塞状态:

①当前线程调用sleep()方法,进入睡眠状态。

②当前线程调用wait()方法。除非线程收到nofify()或者notifyAll()消息,否则不会变成就绪态。

③被其他线程调用了join()方法,等待其他线程结束。

④发出I/O请求,等待I/O操作完成期间。

处于阻塞状态可以重新回到就绪状态,如休眠结束,其他线程加入,I/O操作完成,调用notify()或notifyAll()唤醒wait线程。

5.死亡状态

线程退出run()方法后,就会进入死亡状态(dead)。线程进入死亡状态有可能是正常执行完成run()方法后进入,也有可能是由于发生异常而进入的。

4线程管理

4.1线程优先级

Java提供了10种优先级,分别用1~10整数表示,最高优先级是10,用常量MAX_PRIORITY表示;最低优先级是1,用常量MIN_PRIORITY表示;默认优先级是5,用常量NORM_PRIORITY表示。

Thread类提供了setPriority(int newPriority)方法用以设置线程优先级,通过getPriority()方法可以获得线程优先级。

设置线程优先级示例代码如下:

package duoxiancheng;
public class HelloThread2 {public static void main(String[] args) {//创建线程t1,参数是一个线程执行对象RunnerThread t1=new Thread(new Runner());t1.setPriority(Thread.MAX_PRIORITY);//开始线程t1t1.start();//创建线程t2,参数是一个线程执行对象RunnerThread t2=new Thread(new Runner());t2.setPriority(Thread.MIN_PRIORITY);//开始线程t2t2.start();}
}

多次运行上面的示例会发现,t1线程经常先运行,但是偶尔t2线程也会先运行。这些现象说明,影响线程获得CPU时间的因素,除了线程优先级外,还与操作系统有关。

4.2等待线程结束

在介绍线程状态时提到过join()方法,当前线程调用t1线程的join()方法,则阻塞当前线程,等待t1线程结束,如果t1线程结束或等待超时,则当前线程回到就绪状态。

Thread类提供了多个版本的join(),其定义如下:

void join() :等待这个线程结束。

void join(long millis) :等待这个线程结束的时长最多 millis毫秒。

void join(long millis, int nanos) :等待该线程结束的时间最长为 millis毫秒加上 nanos纳秒。

使用join()方法的场景是,一个线程依赖于另外一个线程的运行结果,所以调用另一个线程的join()方法等它运行完成。

4.3线程让步

线程类Thread还提供一个静态方法yield(),调用yield()方法能够使当前线程给其他线程让步。它类似于sleep()方法,能够使运行状态的线程放弃CPU使用权,暂停片刻,然后重新回到就绪状态。与sleep()方法不同的是,sleep()方法是线程进行休眠,能够给其他线程运行的机会,无论线程优先级高低都有机会运行。而yield()方法只给相同优先级或更高优先级线程机会。yield()方法在实际开发中很少使用,大都使用sleep()方法,sleep()方法可以控制时间,而yield()方法不能。

4.4线程停止

线程体中的run()方法结束,线程进入死亡状态,线程就停止了。但是有些业务比较复杂,例如想开发一个下载程序,每隔一段执行一次下载任务,下载任务一般会由子线程执行,休眠一段时间再执行。这个下载子线程中会有一个死循环,为了能够停止子线程,设置一个结束变量。

示例代码如下:

package duoxiancheng;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class HelloThread3 {public static String command="";      //①public static void main(String[] args) {// TODO Auto-generated method stub//创建线程t1,参数是一个线程执行对象RunnerThread t1=new Thread(()->{//一直循环,直到满足条件再停止线程while(!command.equalsIgnoreCase("exit"))     //②{//线程开始工作//TODOSystem.out.println("下载中......");try {//线程休眠Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}//线程执行结束System.out.println("执行完成!");});//开始线程t1t1.start();try(InputStreamReader ir=new InputStreamReader(System.in);       //③BufferedReader in=new BufferedReader(ir)){//从键盘接收了一个字符串的输入command=in.readLine();       //④} catch (IOException e) {e.printStackTrace();}}
}

上述代码第①行是设置一个结束变量。代码第②行是在子线程的线程体中判断用户输入的是否为exit字符串,如果不是则进行循环,否则结束循环,结束循环就结束了run()方法,线程就停止了。

代码第③行中的System.in是一个很特殊的输入流,能够从控制台(键盘)读取字符。代码第④行是通过流System.in读取键盘输入的字符串。

运行结果:

注意:控制线程的停止有人会想到使用Thread提供的stop()方法,这个方法已经不推荐使用,因为这个方法有时会引发严重的系统故障,类似还有suspend()resume()挂起方法。Java现在推荐的做法就是采用本例的结束变量方式。

5线程安全

5.1临界资源问题

多个线程间共享的数据称为共享资源临界资源,由于是CPU负责线程的调度,程序员无法精确控制多线程的交替顺序。这种情况下,多线程对临界资源的访问有时会导致数据的不一致性。

5.2多线程同步

为了防止多线程对临界资源的访问有时会导致数据的不一致性,Java提供了“互斥”机制,可以为这些资源对象加上一把“互斥锁”,在任一时刻只能由一个线程访问,即使该线程出现阻塞,该对象的被锁定状态也不会解除,其他线程仍不能访问该对象,这就是多线程同步。线程同步是保证线程安全的重要手段,但是线程同步客观上会导致性能下降。

可以使用synchronized关键字通过两种方式实现线程同步:一种是synchronized方法,使用synchronized关键字修饰方法,对方法进行同步;另一种是synchronized语句,将synchronized关键字放在对象前面限制一段代码的执行。

1.synchronized方法

synchronized关键字修饰方法实现线程同步,方法所在的对象被锁定。

以售票系统为例。TicketDB文件代码如下:

package duoxiancheng;
//机票数据库
public class TicketDB {//机票的数量private int ticketCount=5;//获得当前机票数量public synchronized int getTicketCount(){return ticketCount;}//销售机票public synchronized void sellTicket(){try {//线程休眠,阻塞当前线程,模拟等待用户付款Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.printf("第%d号票,已经售出\n",ticketCount);ticketCount--;}
}

调用代码如下:

package duoxiancheng;public class HelloWorld {public static void main(String[] args) {TicketDB db=new TicketDB();//创建线程t1Thread t1=new Thread(()->{while(true){int currTicketCount=db.getTicketCount();//查询是否有票if(currTicketCount>0){db.sellTicket();}else{//无票退出break;}}});//开始线程t1t1.start();//创建线程t2Thread t2=new Thread(()->{while(true){int currTicketCount=db.getTicketCount();//查询是否有票if(currTicketCount>0){db.sellTicket();}else{//无票退出break;}}});//开始线程t2t2.start();}
}

2.synchronized语句

synchronized语句方式主要用于第三方类,不方便修改它的代码情况。其TicketDB.java类方法可以不用加synchronized关键字,但调用代码HelloWorld.java需做修改:

package duoxiancheng;
public class HelloWorld {public static void main(String[] args) {TicketDB db=new TicketDB();//创建线程t1Thread t1=new Thread(()->{while(true){synchronized (db) {int currTicketCount=db.getTicketCount();//查询是否有票if(currTicketCount>0){db.sellTicket();}else{//无票退出break;}}}});//开始线程t1t1.start();//创建线程t2Thread t2=new Thread(()->{while(true){synchronized (db) {int currTicketCount=db.getTicketCount();//查询是否有票if(currTicketCount>0){db.sellTicket();}else{//无票退出break;}}}});//开始线程t2t2.start();}
}

6线程间通信

如果两个线程之间有依赖关系,线程之间必须进行通信,互相协调才能完成工作。

例如有一个经典的堆栈问题,一个线程生成了一些数据,将数据压栈;另一个线程消费了这些数据,将数据出栈。这两个线程互相依赖,当堆栈为空,消费线程无法取出数据时,应该通知生成线程添加数据;当堆栈已满,生产线程无法添加数据时,应该通知消费线程取出数据。

为了实现线程间通信,需要使用Object类中声明的5个方法:

void wait():等待当前线程释放对象锁,然后当前线程处于对象等待队列中阻塞状态,如下图所示,等待其他线程唤醒。

void wait(long timeout):同wait()方法,等待timeout毫秒时间。

void wait(long timeout,int nanos):同wait()方法,等待timeout毫秒加nanos纳秒时间。

void notify():当前线程唤醒此对象等待队列中的一个线程,如下图所示,该线程将进入就绪状态。

void notifyAll():当前线程唤醒此对象等待队列中的所有线程,如下图所示,这些线程将进入就绪状态。

下面是消费和生产示例中堆栈类代码:

package duoxiancheng;
//堆栈类
public class Stack {//堆栈指针初始值为0private int pointer=0;//堆栈有5个字符的空间private char[] data=new char[5];//压栈方法,加上互斥锁public synchronized void push(char c){//堆栈已满,不能压栈while(pointer==data.length){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//通知其他线程把数据出栈this.notify();//数据压栈data[pointer]=c;//指针向上移动pointer++;}//出栈方法,加上互斥锁public synchronized char pop(){//堆栈无数据,不能出栈while(pointer==0){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//通知其他线程压栈this.notify();//指针向下移动pointer--;//数据出栈return data[pointer];}
}

调用代码如下:

package duoxiancheng;
public class HelloWorld {public static void main(String[] args) {Stack stack=new Stack();//下面的消费者和生产者所操作的是同一个堆栈对象stack//生产者线程Thread producer=new Thread(()->{char c;for(int i=0;i<10;i++){//随机产生10个字符c=(char)(Math.random()*26+'A');//把字符压栈stack.push(c);//打印字符System.out.println("生产:"+c);try {//每产生一个字符线程就睡眠Thread.sleep((int)(Math.random()*1000));} catch (InterruptedException e) {e.printStackTrace();}}});//消费者线程Thread consumer=new Thread(()->{char c;for(int i=0;i<10;i++){//从堆栈中读取字符c=stack.pop();//打印字符System.out.println("消费:"+c);try {//每读取一个字符线程就睡眠Thread.sleep((int)(Math.random()*1000));} catch (InterruptedException e) {e.printStackTrace();}}});producer.start();    //启动生产者线程consumer.start();  //启动消费者线程}
}

【Java】多线程编程相关推荐

  1. java多线程编程01---------基本概念

    一. java多线程编程基本概念--------基本概念 java多线程可以说是java基础中相对较难的部分,尤其是对于小白,次一系列文章的将会对多线程编程及其原理进行介绍,希望对正在多线程中碰壁的小 ...

  2. java多线程编程同步方法_实践【Java多线程编程核心技术】系列:同步方法造成的无限等待...

    本文实践来自于[Java多线程编程核心技术]一书! 同步方法容易造成死循环,如-- 类Service.java: package service; public class Service { syn ...

  3. java超线程_超线程多核心下Java多线程编程技术分析

    在学习编程的过程中,我觉得不止要获得课本的知识,更多的是通过学习技术知识提高解决问题的能力,这样我们才能走在最前方,本文主要讲述超线程多核心下Java多线程编程技术分析,更多Java专业知识,广州疯狂 ...

  4. Java多线程编程实战指南

    内容简介 随着CPU 多核时代的到来,多线程编程在充分利用计算资源.提高软件服务质量方面扮演了越来越重要的角色.而解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案.然而,多线程编 ...

  5. Java多线程编程实战指南+设计模式篇pdf

    下载地址:网盘下载 随着CPU 多核时代的到来,多线程编程在充分利用计算资源.提高软件服务质量方面扮演了越来越重要的角色.而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案.然 ...

  6. Java多线程编程那些事:volatile解惑--转

    http://www.infoq.com/cn/articles/java-multi-thread-volatile/ 1. 前言 volatile关键字可能是Java开发人员"熟悉而又陌 ...

  7. Java多线程编程模式实战指南(二):Immutable Object模式--转载

    本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-immutable-o ...

  8. java多线程编程_Java多线程编程实战指南+设计模式篇.pdf

    Java多线程编程实战指南+设计模式篇.pdf 对Java架构技术感兴趣的工程师朋友们可以关注我,转发此文后私信我"Java"获取更多Java编程PDF资料(附送视频精讲) 关注我 ...

  9. java多线程基础视频_【No996】2020年最新 Java多线程编程核心基础视频课程

    01.课程介绍.mp4 02.多线程编程基础-进程与线程.mp4 03.多线程编程基础-使用多线程-继承Thread类.mp4 04.多线程编程基础-使用多线程-实现Runnable接口.mp4 05 ...

  10. Java多线程编程中Future模式的详解

    转载自 https://www.cnblogs.com/winkey4986/p/6203225.html Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker ...

最新文章

  1. 2006校园流行语锋线排行TOP10
  2. 【入门须知】学DIV CSS技术如何入门?
  3. GridView中DropDownList联动
  4. Linux中把文件夹打成war包,SpringBoot中maven项目打成war包部署在liunx服务器上的方法...
  5. python 导航栏_解析导航栏的url--selnium,beautifulsoup实战
  6. html5视频抓取,js和HTML5基于过滤器从摄像头中捕获视频的方法
  7. MYSQL——《数据库》实验壹——熟悉数据库管理工具、数据库和表的基本操作
  8. PyCharm 中选中一个变量/函数后,所有用到这个变量/函数的地方高亮显示,改配色方案
  9. 基于min-max搜索和alpha-beta(α-β)剪枝的五子棋的c语言实现(带简单禁手)
  10. python 正则表达式的应用
  11. overscroll-behavior
  12. Linear-gradient()
  13. 《神雕侠侣》古墓派玉女功养生修炼
  14. Dubbo相关问题如何用管程实现异步转同步?
  15. python爬虫入门教程04:招聘信息爬取
  16. 欧阳的科研历程-1 目标
  17. 计算机的硬盘维修,计算机硬盘的维修方法和技巧
  18. 2019年10月8日股市走势预测
  19. linux vi编辑文件的时候未正常关闭,产生交换文件.swp
  20. 个人工作、学习常用网站

热门文章

  1. 数据库设计三大范式详解
  2. ssl证书的生成与签名
  3. Android版本升级同时Sqlite数据库的升级及之前数据的保留-转
  4. SpringBoot之Filter过滤器的实现及排序问题
  5. [Java] 蓝桥杯BASIC-25 基础练习 回形取数
  6. PAT 乙级 1017. A除以B (20) Java版
  7. 【软件体系结构】重用的粒度的定义
  8. L1-007. 念数字-PAT团体程序设计天梯赛GPLT
  9. 我的世界java骷髅马_我的世界:骷髅马材质更新,老MC教你获得骷髅马技巧,萌新:真好...
  10. python的datetime模块用法_Python3.5内置模块之time与datetime模块用法实例分析