线程小总结
在计算机中,单核计算机只能存在一个管程,管程是由进程组成的,所谓的进程,是在计算机中运行的软件或程序,如你正在使用的浏览器,eclipse,QQ,音乐播放器等,在一个软件中,如在浏览器中,你可以看视频,同时在另一个页面是看其他的东西,或回复你对这个视频的看法,此时,你发现,在一个软件中,可以同时做不同的事,且它们之间不会相互干扰,在软件中,对你做不同的事的控制的是进程。总的来说,管程可分为进程,进程可分为线程。
多线程对一个好程序来说,有时是必需的,可以想象,一个程序只有一个线程时,如你的多米音乐播放器运行时,只能听歌,而不能同步歌词,或在听的时候,想要下载这首歌只能再去运行另一个程序去完成,这就非常麻烦。另外,许多程序也只有多线程才能完成,例如游戏,得分,时间的改变,是要同步的。
建立线程。
一般有两种方法,第一个是实例化Thread类,第二个是实例化Runnable接口,实例完成后在需要的时候再通过start()方法启动。
如下面代码:
建立线程的第一种方法
package threadTest0115;
public class ThreadSample02 implements Runnable {
public static void main(String[] args) { //实例线程 Thread thread = new Thread(new ThreadSample02()); //启动线程 thread.start(); System.out.println("main方法完毕"); }
/** * 从接口Runnable中继承的方法 */ public void run() { //新线程要实现的功能的代码 System.out.println("接口Runnable中继承的方法run的执行"); }
}
|
运行的输出是:
建立线程的第二种方法
package threadTest0115;
public class ThreadSample extends Thread{
public static void main(String[] args) { //线程的实例 ThreadSample thread=new ThreadSample(); //线程的启动 thread.start(); System.out.println("main方法完毕"); } /** * 从类Thread中继承的方法 */ public void run(){ //新线程要实现的功能的代码 System.out.println("类Thread中继承的方法run的执行"); }
}
|
运行的结果是:
通过上面的例子可以看出,所谓的建立新的线程,就是实例一个线程对象,然后通过start方法启动后,进行到run方法中执行,而不影响原来的部分程序的顺序执行。上面的运行结果都显示了main方法中的代码执行完成后,整个程序并未有结束,建立的线程还在执行。
其实main方法也可看作是自动为程序建立的第一个线程。且main()方法的线程称为主线程,用户新建的线程称为子线程
上面的程序运行的结果都是先完成main方法的线程,然后完成新建的线程中的run方法,这并不表示这两个线程有什么先后顺序,而只是切换线程时开销比较大,所用的时间长而已。
一般情况下,要建立一个类继承Thread类或Runnable接口,再重写Thread类或Runnable接口中的run方法,达到建立新线程的目的,否则,新建的线程无意义。
一个线程有6种状态,称为的生命周期。
NEW:实例但未启动,称为新建状态;
RUNNABLE:正在JVM中执行,称为可运行状态
调用了start()方法,但此时线程不一定立即开始执行,要由操作系统分配时间。
时间片执行完毕后会打断它的执行。
BLOCKED:受阻塞并等待某个监视器锁,称为阻塞状态
WAITING:无限期地等待另一个线程来执行某一个特定操作,称为等待状态
TIMED_WAITING:等待另一个线程来执行取决于指定等待时间的操作,称为超时等待状态
TERMINATED:已经退出的线程时,称为中止状态。Run()方法正常执行完毕,或是由异
常事件终止了run()方法。
对线程有一些简单的操作,如下:
线程睡眠:使用语句Thread.sleep(整型毫秒数);这个会抛出异常。进入超时等待状态
线程让步:使用语句Thread.yield();把执行的机会让给相同或者更高优先级的线程。
线程加入:使用join()方法,格式是:子线程标识.join();使交叉执行的线程变成顺序
执行。
建立并启动了线程后,线程之间,即子线程与子线程之间或是主线程与子线程之间是并行执行的,而这并不是说同一非常短的时间中多个线程一起执行了。现在计算机的操作系统多采用分时操作系统,在cpu中时间看成长轴,它被分为一截截,称为时间片,在同一时间片中,只能执行一个线程。而这个时间片非常短,许多不同的线程都有机会执行到,让人感觉这些线程是在同时执行的。启动了的线程加入JVM中,由cpu调度,当某个线程获得一个时间片时,它就在这个时间片内运行,当这个时间片过了,即使线程未执行完毕也不再真正在执行了,而是等待另一个时间片再真正执行。cpu使用了时间分片可以提高它的效率,但通过上述,很容易想到,当两个或以上的线程对同一段代码进行执行时,第一个线程执行了一部分代码,第二个线程也执行了一部分或是全部代码时,共享代码中的数据的在第一个线程中有一些改变但不完全,第二个线程有一些或完全的改变,那么这两个线程共享代码中的数据改变得如何就不确定了。如如下例子:
package threadTest0116;
public class Test01 extends Thread { //要改变的属性 private int x; //程序入口,开始主线程 public static void main(String[] args) { //实例Thread的子类,建立了线程 Test01 t = new Test01(); //启动线程 t.start(); //无限的循环,对类属性进行加与输出 while (true) { t.add(); t.dis(); try { //线程休眠15毫秒 Thread.sleep(15); } catch (InterruptedException e) { e.printStackTrace(); } }
} /** * 从Thread继承的方法,启动线程后,跳到此方法中与主线程并发执行 */ public void run() { //无限的循环,对类属性进行加与输出 while (true) { add(); dis(); try { //线程休眠15毫秒 Thread.sleep(15); } catch (InterruptedException e) { e.printStackTrace(); } }
} /** * 对类属性进行加的方法 */ public void add() { this.x += 1; } /** * 输出类属性的值的方法 */ public void dis() { System.out.println("x=" + x); }
}
|
截取这个程序的一部分输出,如下:
这个程序中,主线程与子线程都是对类属性x进行了加1后再输出,那么从逻辑分析来说,x是不可能相同的,但从输出的结果来看,却与此相反。这就是上述据说的多线程输出的结果的不确定。
至于解决这个问题,就需要线程的同步。线程同步的概念与上述的线程的并发执行一样,不可根据它的字面意思理解。事实上,线程同步的真实意义与字面意义正好相反。线程同步是在对同一段共享的代码,一次只能由一个线程进行执行。上面的一个例子,线程并没有同步,可以称为线程异步。
让线程同步的方法一般有三种:
第一种:
使用synchronized关键字对共享的代码进行修饰。
如:
权限修饰符 synchronized 返回值 方法名{
//共享的代码
}
这样做则会使这个方法在一次时只由一个线程执行,使得线程的同步。
又如:
权限修饰符 synchronized 返回值 方法名{
//非共享的代码
synchronized(对象锁){
//共享的代码
}
//非共享的代码
}
这样做则会使这个方法在一次时方法中的非共享代码可由多个线程执行,但共享的代码只能由一个线程执行。对象锁在调用这个方法中的线程中要一致,否则无法达到目的。
第二种方法:
这种方法使用显式加锁,即使用java.util.concurrent.locks.Lock接口中的lock()方法来获取锁。
如:
Private Lock lock=new ReentrantLock();//Lock实例
权限修饰符 返回值 方法名{
//非共享的代码
lock.lock();//获取锁
//共享的代码
lock.unlock();//释放锁
//非共享的代码
}
这样做可很明显看出,这个方法在一次时方法中的非共享代码可由多个线程执行,但共享的代码只能由一个线程执行。要注意的是Lock实例在调用这个方法的线程中必须是一样的,否则无法实现同步。
第三种方法:
这种方法使用了synchronized关键字与java.lang.Object类中的wait(),notify(),notifyAll()等方法共同构成。这种方法非常适合需要线程间的沟通的线程同步。
我们想象这样的一种场景,你上网购买了一件东西,商家将这件东西发送到快递店,当快递店收到东西时,给你发短信,告诉你来取。下面用代码来实现这一场景。
假设你不停地从网店中购买商品,当你购买时,网店将商品发送到快递店中,快递店收到商品后,给你发信息,你就去取商品,但快递店有一定的空间,不能无限量地接收商品,当达到限定量时,给网店发信息,不再发送商品。
先将java.lang.Object中的三个方法作一个介绍:
wait():当前对象终止运行,并放弃对象锁,也可理解成线程阻塞了。
notify():当执行某个对象的notify()方法时,不放弃对象锁,唤醒引用对象在等待池中的一
个线程,使其进 入可运行状态,
notifyAll():唤醒所有在等待这个对象的线程,使它们成为可运行状态,此线程不放弃对象锁。
这里我将此场景分别设置成五个类,分别如下:
1.商品类:通过商品的ID区别商品,设置一个方法得到商品名
package threadTest0116; /** * 商品类,通过ID分别不同的商品 * @author zhong * */ public class Product { //商品ID private int id;
public Product(int id) { this.id = id; } //得到不同的商品名 public String toString() { return id + "product"; } }
|
2.快递店类:有网店送来商品和消费者取走商品的方法
package threadTest0116;
import java.util.List; /** * 快递店类 * @author zhong * */ public class ExpressStore { //商品队列 private List<Product> list; //商品ID,标注不同的商品 private int id = 0;
public ExpressStore(List<Product> list) { this.list = list; } /* * 商家把商品送到快递店,增加快递店的商品 */ public void addProduct() { //锁住要共享的数据的代码 synchronized (list) { if (list.size() > 5) { try { //快递店的商品过多 ,此线程进入等待 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { //增加商品,加入队列 id++; Product obj = new Product(id); list.add(obj); System.out.println("快递店收到了商品:" + obj.toString()); //通知另一个线程启动 list.notify(); } } } /* * 消费者取走商品 */ public void getProduct() { //锁住要共享的数据的代码 synchronized (list) { if (list.size() <= 0) { try { //快递店中没有消费者的商品,线程进入等待 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { //消费者在快递店中取走商品 Product pro = list.get(list.size() - 1); System.out.println("消费者取走了商品:" + pro.toString()); list.remove(list.size() - 1); //通知另一个线程启动 list.notify(); } } }
}
|
3.消费者类:继承Thread类,通过循环,不停地取走商品
package threadTest0116; /** * 消费者类, * @author zhong * */ public class Buyer extends Thread { //快递店实例 private ExpressStore es;
public Buyer(ExpressStore es) { this.es = es; }
public void run() { //循环取走商品 while (true) { try { //休眠 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //取走商品 es.getProduct();
}
}
} |
4.网店类:不停地往快递店送商品
package threadTest0116; /** * 网店类 * @author zhong * */ public class Seller extends Thread { //快递店实例 private ExpressStore es;
public Seller(ExpressStore es) { this.es = es; }
public void run() { //循环给快递店送商品 while (true) { //给快递店送商品 es.addProduct(); try { //休眠 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }
} |
5.程序入口,启动各个线程:
package threadTest0116;
import java.util.ArrayList; import java.util.List; /** * 程序入口 * @author zhong * */ public class DeliveryTest {
public static void main(String[] args) { //商品队列 List<Product> list = new ArrayList<Product>(); //快递店类实例 ExpressStore exst = new ExpressStore(list); //消费者类实例 Buyer buyer = new Buyer(exst); //网店实例 Seller seller = new Seller(exst); //启动消费者线程 buyer.start(); //启动网店线程 seller.start(); }
}
|
这个线程的输出如下:
这里的代码要注意的是不要造成死锁,死锁是指线程间的相互等待资源,导致程序阻塞。对不同线程中的wait()方法与notify()方法要通过同一个对象调用,synchronized中的对象锁也要和调用wait()方法与notify()方法的对象一致。
上述的程序采用了生产消费模型,通过快递店联结消费者与网店,趋向代偶合的目的。
相关推荐
1. 设置线程状态为可运行。 2. 调用`native`方法`start0()`,这是一个本地方法,用于实际启动线程。`native`关键字表明该方法的实现位于Java虚拟机之外,通常是操作系统层面的线程调度。 ### 总结 Java多线程提供...
### Java多线程编程总结 #### 一、Java线程:概念与原理 - **操作系统中线程和进程的概念** 当前的操作系统通常都是多任务操作系统,多线程是一种实现多任务的方式之一。在操作系统层面,进程指的是内存中运行的...
1. **使用全局变量**:全局变量是所有线程共享的,因此可以直接用来传递数据。为了确保线程安全,对全局变量的访问应使用`volatile`关键字,以防止编译器优化导致的意外行为。如果需要传递复杂数据结构,可以定义...
【Windows多线程总结】 Windows操作系统提供了一套完整的API来支持多线程编程,使得开发者可以在同一进程中同时执行多个线程,实现并发处理任务。本文将深入探讨Windows多线程编程的基本概念、线程同步、线程池以及...
### Java多线程编程总结 ...通过以上总结,我们可以看出Java5之后对多线程的支持有了极大的提升,引入了一系列的新特性,使得开发者能够更加高效地编写多线程程序。这些特性和概念对于理解Java多线程编程至关重要。
1. **单例模式**:确保一个类只有一个实例,通常需要考虑线程安全的单例实现,如双重检查锁定。 2. **生产者消费者模式**:通过队列来协调生产者和消费者的执行,避免直接的资源竞争。 3. **读写锁**:允许多个线程...
Java多线程是Java编程中的一个核心概念,它在现代软件开发中扮演着至关重要的角色。多线程允许程序同时执行多个任务,提高了系统资源的利用率,提升了应用程序的响应速度和并发性能。对于大型分布式系统、Web应用...
【多线程精心总结】 在Java编程中,多线程是一种重要的并发处理方式,它可以提高程序的执行效率,尤其在处理大量并发任务时。本文将深入探讨Java线程中的几个关键概念,包括`yield()`、`sleep()`、`wait()`、`join...
Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...
C#.net同步异步SOCKET通讯和多线程总结 C#.net同步异步SOCKET通讯和多线程总结是指在C#.net环境下实现的同步异步套接字通信和多线程编程的总结。套接字(Socket)是tcp/ip网络协议接口,内部定义了许多的函数和例程...
1. **线程基础** - 线程是操作系统分配CPU时间的基本单位,一个进程可以包含一个或多个线程。 - Java中通过`java.lang.Thread`类或者实现`Runnable`接口来创建线程。 - 主线程:每个Java应用程序都有一个主线程,...
### 线程总结笔记——基于Linux环境下的线程控制与同步 #### 一、引言 本篇“线程总结笔记”主要针对Linux环境下多线程编程中的关键概念进行了整理与归纳,尤其是针对线程同步的问题进行了深入探讨。通过一个具体...
【JAVA多线程总结】 Java 多线程是Java编程中的关键特性,它允许程序同时执行多个任务,提高系统的效率和响应性。本篇总结涵盖了Java多线程的基础概念、创建与启动、线程调度、同步与协作以及新特性。 **一、Java...
1. **线程的创建** Java提供了两种创建线程的方式: - **继承Thread类**:自定义类继承Thread类,并重写run()方法,然后通过创建该类的实例并调用start()方法启动线程。 - **实现Runnable接口**:创建一个实现了...
C#.net 同步异步 SOCKET 通讯和多线程总结 本文旨在总结 C#.net 中的同步异步 SOCKET 通讯和多线程编程,涵盖服务端和客户端的实现细节。 一、服务端实现 服务端使用 System.Net 和 System.Net.Sockets 命名空间...
多线程是指在一个程序中包含多个控制流,它们可以并发执行不同的任务。在Java中,多线程的实现通常借助于`Thread`类或实现`Runnable`接口。多线程能够提高CPU的利用率,改善应用程序性能。 #### 2. Java中的线程...
C++多线程总结 本文档对C++多线程编程进行了总结,介绍了三种创建线程的方法:CreateThread函数、AfxBeginThread函数和_beginthread()/_beginthreadex()函数,同时对线程的管理和终止进行了详细的讲解。 ...
线程优先级也是Java线程的一个特性,每个线程都有一个优先级,`Thread.NORM_PRIORITY`是默认优先级,`Thread.MIN_PRIORITY`和`Thread.MAX_PRIORITY`分别是最低和最高优先级。但是,线程优先级并不保证绝对的执行顺序...
使用 `thread apply ID1 ID2 command` 命令可以让一个或者多个线程执行 GDB 命令 command。 使用 `thread apply all command` 命令可以让所有被调试线程执行 GDB 命令 command。 scheduler-locking 命令 使用 `...
### Java线程总结教程知识点详解 #### 一、操作系统与多线程概念 - **多任务与分时操作系统**:现代操作系统(如Windows、Linux)能够实现多任务处理,即在用户看来似乎多个应用程序在“同时”运行。实际上,这是...