关键词:线程、Thread、Runnable、sleep()、yield()、join()、同步
 
一、线程的概述

在一个操作系统中,每个独立执行的程序都可以称为一个进程,也就是“正在运行的程 序”。而在进程中还可以有多个执行单元同时执行,这些执行单元可以看作程序执行的一条 条线索,被称为线程。Java 运行环境是一个包含了不同的类和程序的单一进程。线程可以被 称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。

二、线程的创建

1、Java提供了两个多线程实现方式:
①一种继承java.lang 包下的Thread类,复写了Thread类的run( )方法,在run( )方法中实现运行在线程上的代码,提供一个start()方法用于启动新线程。
创建对象示例如:MyThread myTread = new MyTread();
 1 public class Example02 {
2 public static void main(String[] args) {
3 MyThread myThread = new MyThread();//创建线程MyThread的线程对象
4 myThread.start();//开启线程
5 while (true){ //通过死循环语句打印输出
6 System.out.println("main()方法在运行");
7 }
8 }
9 }
10 class MyThread extends Thread{
11 public void run(){
12 while (true){
13 System.out.println("MyThread 类的run()方法");
14 }
15 }
16 }
②一种实现java.lang.Runnable接口,同样在run( )方法中实现运行在线程上的代码,提供一个start()方法用于启动新线程。
创建对象示例如:MyThread myTread = new MyTread();
Thread thread = new Thread(myTread);
 1 public class Example03 {
2 public static void main(String[] args) {
3 MyThread myThread = new MyThread();//创建线程MyThread的线程对象
4 Thread thread = new Thread(myThread);//创建线程对象
5 thread.start();//开启线程,执行线程中的run()方法
6 while (true){ //通过死循环语句打印输出
7 System.out.println("main()方法在运行");
8 }
9 }
10 }
11 class MyThread implements Runnable{
12 public void run(){ //线程的代码段,当调用start()方法时,线程从此处开始
13 while (true){
14 System.out.println("MyThread 类的run()方法");
15 }
16 }
17 }
 
2、实现Runnable接口相对于继承Thread类来说有如下好处:
①适合多个相同的程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据有效的分离;
②可以避免由于java的单继承带来的局限性(一个类一旦继承了某个父类就无法再继承Thread类)。
 
3、所谓的后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此当所有的非后台线程结束时,程序也就终止了,同时会杀死所有后台线程。反过来说,只要有任何非后台线程(用户线程)还在运行,程序就不会终止。后台线程在不执行finally子句的情况下就会终止其run方法。后台线程创建的子线程也是后台线程。
在某个线程对象在启动之前(start()方法之前)调用setDaemon(true)语句,这个线程就变成一个后台线程。
 
三、线程的生命周期及状态转换
 
     

1、新建状态(new)
此时不能运行,仅仅由Java虚拟机为其分配内存。
2、就绪状态(Runnable)
当线程对象调用start()方法后,该线程就进入就绪状态(也称可运行状态),位于运行池中,此时已具备运行条件,能否获得CPU的使用权开始运行,还需要等待系统的调度。
3、运行状态(Running)
就绪线程获得CPU使用权,开始执行run()方法中的线程执行体,则该线程处于运行状态。只有处于就绪状态的线程才能转换到运行状态。
4、阻塞状态(Blocked)
由阻塞状态状态转换成就绪状态:
①当线程试图获取某个对象的同步锁时,如果该锁被其他线程所持有,则当前线程会进入阻塞状态,如果想从阻塞状态进入就绪状态必须得获取到其他线程所持有的锁。
②当线程调用了一个阻塞式IO方法时,该线程就会进入阻塞状态,如果想进入就绪状态就必须要等到这个阻塞的IO方法返回。
③当线程调用了某个对象的wait()方法时,也会使线程进入阻塞状态,如果想进入就绪状态就需要使用notify()方法唤醒该线程。
④当线程调用了Thread的sleep(long mills)方法时,也会使线程进入阻塞状态,在这种情况下,只需等到线程睡眠的时间到以后,线程就会自动进入就绪状态。
⑤当一个线程中调用了另一个线程的join()方法时,会使当前线程进入阻塞状态,在这种情况下,需要等到新加入的线程运行结束后才会结束阻塞状态,加入就绪状态。
注意:线程从阻塞状态只能进入就绪状态而不能直接进入运行状态。
5、死亡状态(Terminated)
线程run()执行完或线程抛出未捕获异常、错误,线程就会进入死亡状态。一旦进入死亡状态,线程将不再拥有运行的资格,也不能转换到其他状态。
 
四、线程的调度
1、线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。如:thread.setPriority(10);
默认情况下,每一个线程都会分配一个普通优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
 
2、线程休眠
Thread.sleep(long millis)方法声明抛出InerruptedException异常,因此在调用此方法时应该捕获异常,或者声明抛出该异常。该方法静态方法,只能控制当前线程休眠。

 1 public class Example08 {
2 public static void main(String[] args) throws Exception{
3 // 创建一个线程
4 new Thread(new SleepThread()).start();
5 for (int i= 1;i<=10;i++){
6 if (i==5){
7 Thread.sleep(2000);//当前线程休眠2秒
8 }
9 System.out.println("主线程正在输出:"+i);
10 Thread.sleep(500);//当前线程休眠500毫秒
11 }
12 }
13 }
14 class SleepThread implements Runnable{
15 @Override
16 public void run() {
17 for (int i= 1;i<=10;i++){
18 if (i==3){
19 try{
20 Thread.sleep(2000);
21 }catch (InterruptedException e ){
22 e.printStackTrace();
23 }
24 }
25 System.out.println("线程一正在输出:"+i);
26 try{
27 Thread.sleep(500);
28 } catch (Exception e ){
29 e.printStackTrace();
30 }
31 }
32
3、线程让步
线程让步可以通过yield()方法实现,该方法与sleep()方法相似,都是可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞该线程,它只是将该线程转换成就绪状态,让系统的调度器重新调度一次。当某个线程调用yield()方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。
 1 //定义YieldThread类继承Thread类
2 class YieldThread extends Thread{
3 // 定义一个有参构造方法
4 public YieldThread(String name){
5 super(name);//调用父类的构造方法
6 }
7
8 @Override
9 public void run() {
10 for (int i= 0;i<5;i++){
11 System.out.println(Thread.currentThread().getName()+"___"+i);
12 if (i==3){
13 System.out.print("线程让步");
14 Thread.yield();//线程运行至此,做出让步
15 }
16 }
17 }
18 }
19 public class Example09 {
20 public static void main(String[] args) {
21 //创建两个线程
22 Thread t1= new YieldThread("线程A");
23 Thread t2= new YieldThread("线程B");
24 //开启两个线程
25 t1.start();
26 t2.start();
27 }
28
29 }
 
4、线程插队
当某个线程中调用其他线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完成后它才会继续运行。
 1 public class Example10 {
2 public static void main(String[] args) throws Exception{
3 // 创建线程
4 Thread t = new Thread(new EemergencyThread(),"线程一");
5 t.start();
6 for (int i=1;i<6;i++){
7 System.out.println(Thread.currentThread().getName()+"输入:"+i);
8 if (i==2){
9 t.join(); //调用join()方法
10 }
11 Thread.sleep(500);
12 }
13 }
14 }
15 class EemergencyThread implements Runnable{
16 @Override
17 public void run() {
18 for (int i =1;i<6;i++){
19 System.out.println(Thread.currentThread().getName()+"输入:"+i);
20 try{
21 Thread.sleep(500);
22 }catch (InterruptedException e){
23 e.printStackTrace();
24 }
25 }
26 }
27 }
五、多线程同步
1、同步代码块:lock是一个锁的对象,它是同步代码块的关键,它可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的。“任意”说的是共享锁对象的类型。
  synchronized(lock){ 操作共享资源代码块 } 
2、同步方法:
synchronized 返回值类型 方法名([参数1,......]){ }

3、同步代码块和同步方法解决多线程同时访问共享数据时的线程安全问题,只要加上同一个锁,在同一时间内只能有一条线程执行,但是线程在执行同步代码时每次都会判断锁的状态,非常消耗资源,效率较低。

      示例:
 1 //定义Ticket1类实现Runnable接口
2 class Ticket1 implements Runnable{
3 private int tickets=10; //定义变量tickets,并赋值10
4 /* 同步代码块用法
5 Object lock = new Object();//定义任意一个对象,用做同步代码块的锁
6 public void run(){
7 while (true){
8 synchronized(lock){
9 try {
10 Thread.sleep(10);
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 if(tickets>0){
15 System.out.println(Thread.currentThread().getName()
16 + "---卖出的票" + tickets--);
17 }else{
18 break;
19 }
20 }
21 }
22
23 }*/
24
25 public void run(){
26 while (true){
27 saleTicket(); //调用售票方法
28 if (tickets<=0){
29 break;
30 }
31 }
32
33 }
34 //定义一个同步方法saleTicket()
35 private synchronized void saleTicket (){
36 if (tickets>0) {
37 try {
38 Thread.sleep(10);
39 } catch (InterruptedException e) {
40 e.printStackTrace();
41 }
42 System.out.println(Thread.currentThread().getName() +
43 "---卖出的票" + tickets--);
44 }
45 }
46 }
47 public class Example12 {
48 public static void main(String[] args) {
49 Ticket1 ticket =new Ticket1();
50 new Thread(ticket,"线程1").start();
51 new Thread(ticket,"线程2").start();
52 new Thread(ticket,"线程4").start();
53 new Thread(ticket,"线程3").start();
54 }
55 }
      4、死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

java 死锁产生的四个必要条件:

①互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

②不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

③请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

④循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

六、多线程通信
      在Object类中提供了wait(),notify(),notifyAll()方法用于解决线程的通信问题,即让线程按照一定的顺序轮流执行。
      唤醒线程的方法:
方法声明 功能描述
void wait() 使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()方法或notifyAll()方法唤醒该线程为止
void notify() 唤醒此同步锁上等待的第一个调用wait()方法的线程
void notifyAll() 唤醒此同步锁上调用wait()方法的所有线程
       wait(),notify(),notifyAll()这三个方法的调用者都应该是同步锁对象,如果这三个方法的调用者不是同步锁对象,Java虚拟机就会抛出IllegalMonitorStateException 。  
       示例存取数据:
 1 class Storage{
2 private int[] cells =new int[10];// 数组存储数组
3 private int inPos,outPos;// inPos表示存入时数组下标,outPos表示取出时数组下标
4 private int count;//存入或取出数据的数量
5 public synchronized void put(int num){ //定义一个put()方法向数组中存入数据
6 try {
7 //如果放入数据等于cells的长度,此线程等待
8 while (count == cells.length) {
9 this.wait();
10 }
11 cells[inPos] = num;//向数组中放入数据
12 System.out.println("cells[" + inPos + "]中放入数据---" + cells[inPos]);
13 inPos++;//存完元素让位置加1
14 if (inPos == cells.length)
15 inPos = 0;//当inPos为数组长度是10,将其置为0
16 count++;
17 this.notify();
18 }catch (Exception e){
19 e.printStackTrace();
20 }
21 }
22 //定义一个get()方法从数组中取出数据
23 public synchronized void get(){
24 try{
25 while (count==0){
26 this.wait();
27 }
28 int data=cells[outPos];
29 System.out.println("从cells["+outPos+"]中取出数据"+data);
30 cells[outPos]=0;//取出后,当前数据置零
31 outPos++; //取完元素后让位置加1
32 if (outPos==cells.length)
33 outPos=0;
34 count--;//每取出一个元素count减1
35 this.notify();
36 }catch (Exception e){
37 e.printStackTrace();
38 }
39 }
40 }
41 class Input implements Runnable{ //输入线程类
42 private Storage st;
43 private int num; //定义一个变量num
44 Input(Storage st){ //通过构造方法接收一个Storage对象
45 this.st=st;
46 }
47 public void run(){
48 while (true){
49 st.put(num++);//将num存入数组,每次存入后num自增
50 }
51 }
52 }
53 class Output implements Runnable{ //输出线程类
54 private Storage st;
55 Output(Storage st){ //通过构造方法接收一个Storage对象
56 this.st=st;
57 }
58 public void run(){
59 while (true){
60 st.get();//循环取出元素
61 }
62 }
63 }
64 public class Example17 {
65 public static void main(String[] args) {
66 Storage st=new Storage();//创建数组存储类对象
67 Input input=new Input(st);//创建Input对象传入Storage对象
68 Output output=new Output(st);//创建Output对象传入Storage对象
69 new Thread(input).start();//开启新线程
70 new Thread(output).start();//开启新线程
71
72 }
73 }

运行结果

cells[1]中放入数据---214311
cells[2]中放入数据---214312
cells[3]中放入数据---214313
cells[4]中放入数据---214314
cells[5]中放入数据---214315
cells[6]中放入数据---214316
cells[7]中放入数据---214317
cells[8]中放入数据---214318
cells[9]中放入数据---214319
cells[0]中放入数据---214320
从cells[1]中取出数据214311
从cells[2]中取出数据214312
从cells[3]中取出数据214313
从cells[4]中取出数据214314
从cells[5]中取出数据214315
从cells[6]中取出数据214316
从cells[7]中取出数据214317
从cells[8]中取出数据214318
从cells[9]中取出数据214319
从cells[0]中取出数据214320

多线程累加例子:

模拟10个线程,第一个线程从1加到10,第二个线程从11加到20.....第十个线程从91加到100,最后在把10个线程结果相加。

 1 public class Accumulator extends Thread {
2 private int stratNum;
3 public static int sum;
4 public Accumulator(int startNum) {
5 this.stratNum = startNum;
6 }
7 public static synchronized void add(int num) {
8 sum += num;
9 }
10 public void run() {
11 int sum = 0;
12 for (int i = 0; i < 10; i++) {
13 sum += stratNum + i;
14 }
15 add(sum);
16 }
17 public static void main(String[] args) throws Exception {
18 Thread[] threadList = new Thread[10];
19 for (int i = 0; i < 10; i++) {
20 threadList[i] = new Accumulator(10 * i + 1);
21 threadList[i].start();
22 }
23 for (int i = 0; i < 10; i++) {
24 threadList[i].join();
25 }
26 System.out.println("Sum is : " + sum);
27 }
28 }

Java入门——(4)多线程的更多相关文章

  1. Java入门到精通——基础篇之多线程实现简单的PV操作的进程同步

    Java入门到精通——基础篇之多线程实现简单的PV操作的进程同步 一.概述     PV操作是对信号量进行的操作.     进程同步是指在并发进程之间存在一种制约关系,一个进程的执行依赖另一个进程的消 ...

  2. Java开发知识之Java入门

    Java开发知识之Java入门 一丶了解JAVA的版本 JAVA 有三个版本 JAVA SE: 标准版,开发桌面跟商务应用程序 JAVA SE 包括了Java的核心类库,集合,IO 数据库连接 以及网 ...

  3. Java 入门进阶

    Java 入门进阶 發表於 2015-04-16 http://xielong.me/2015/04/16/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%B7%A5%E7%A8%8B% ...

  4. java入门概念梳理总结

    Java入门学习 简介 public class HelloWorld { public static void main(String []args) { System.out.println(&q ...

  5. java入门到秃路线导航,元芳你怎么看?【教学视频+博客+书籍整理】

    目录 一.Java基础 二.关于JavaWeb基础 三.关于数据库 四.关于ssm框架 五.关于数据结构与算法 六.关于开发工具idea 七.关于项目管理工具Mawen.Git.SVN.Gradle. ...

  6. Java入门——初识Java

    Java入门——初识Java 摘要:本文主要对Java这门编程语言进行简单的介绍. Java简介 说明 Java语言历时十多年,已发展成为人类计算机史上影响深远的编程语言,从某种程度上来看,它甚至超出 ...

  7. Java入门学习路线目录索引

    原创 Java入门学习路线目录索引 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/One_ ...

  8. Java入门和环境配置ideaJ安装

    Java入门及环境搭建 目录 Java入门及环境搭建 什么是Java Java Java的发展 Java的特性和优势 Java三大版本 JDK JRE JVM JAVA开发环境搭建 安装JDK 卸载J ...

  9. 三、Java入门

    Java入门 Java的特性和优势 特性 ​ 简单 ​ 面对对象 ​ 可移植性 优势 ​ 性能高 ​ 分布式(跨平台:Write Once .Run Anywhere) ​ 动态性(反射) ​ 多线程 ...

  10. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

随机推荐

  1. SequoiaDB版本在线升级介绍说明

    1.前言 在SequoiaDB数据库发展过程中,基本保持每半年对外发行一个正式的Release版本.并且每个新发布的Release版本相对老版本而言,性能方面都有很大的提高,并且数据库也会在新版本中加 ...

  2. 从Android源码的角度分析Binder机制

    欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 前言 大家好,好久不见,距离上篇文章已经有35天之久了,因为身体不舒服 ...

  3. HTML DOM元素关系与操作

    <html> <head><title>DOM元素关系与操作</title></head> <body> <!-- div ...

  4. tomcat7以上,ajax post参数后台获取不到的问题

    AJAX post传参后台获取不到查询参数. 网上找了各种方法,包括设置content-type,又是把json转成json格式字符串,问题依然存在,但是把post改成get又可以获取到,百思不得其解 ...

  5. 基于Metronic的Bootstrap开发框架经验总结(15)-- 更新使用Metronic 4.75版本

    在基于Metronic的Bootstrap开发框架中,一直都希望整合较新.较好的前端技术,结合MVC的后端技术进行项目的开发,随着时间的推移,目前Metronic也更新到了4.75版本,因此着手对这个 ...

  6. Struts2框架05 result标签的类型

    1 result标签是干什么的 就是结果,服务器处理完返回给浏览器的结果:是一个输出结果数据的组件 2 什么时候需要指定result标签的类型 把要输出的结果数据按照我们指定的数据类型进行处理 3 常 ...

  7. Git操作大全[实际用到的都放在这里总结]

    1.如何合并远程两个分支feature-rebuild和develop? g fetch g checkout -b develop origin/develop g merge feature-re ...

  8. jmeter问题处理随笔1 - 自动遍历用例(一次)

    背景: 弄了个无业务关联的巡检接口方案,需要循环获取csv中的数据,每一条数据,运行一次,直到全部运行完,但是使用后发现要通过[线程组]中设置循环变量的数据为来读取csv中的数据,这样每次修改csv用 ...

  9. 机器学习 —— 基础整理(六)线性判别函数:感知器、松弛算法、Ho-Kashyap算法

    这篇总结继续复习分类问题.本文简单整理了以下内容: (一)线性判别函数与广义线性判别函数 (二)感知器 (三)松弛算法 (四)Ho-Kashyap算法 闲话:本篇是本系列[机器学习基础整理]在time ...

  10. PPK提供的浏览器类型及版本检测方法

    PPK提供的浏览器类型及版本检测方法   一个常用但是被高估的Javascript函数就是浏览器检测.有些时候,你想给出一个说明或者加载一个页面来提示用户,以免使用Safari等浏览器. 使用方法: ...