java多线程的应用场景模拟现实生活场景

利用多线程技术模拟微信抢红包 要完整java程序_百度知道6740人阅读
Core Java(25)
Java多线程是什么
Java提供的并发(同时、独立)处理多个任务的机制。多个线程共存于同一JVM进程里面,所以共用相同的内存空间,较之多进程,多线程之间的通信更轻量级。依我的理解,Java多线程完全就是为了提高CPU的利用率。Java的线程有4种状态,新建(New)、运行(Runnable)、阻塞(Blocked)、结束(Dead),关键就在于阻塞(Blocked),阻塞意味着等待,阻塞的的线程不参与线程分派器(Thread
Scheduler)的时间片分配,自然也就不会使用到CPU。多线程环境下,那些非阻塞(Blocked)的线程会运行并充分利用CPU。
Java线程阻塞(Blocked)的类型:
调用sleep函数进入睡眠状态,Thread.sleep(1000)或者TimeUnit.SECONDS.sleep(1),sleep不会释放锁。等待(wait)某个事件,分为两种,(wait,notify,notifyAll),(await, signal,signalAll) ,后面会详细介绍。wait和await会释放锁,且必须在获取到锁的环境才能调用。等待锁,synchronized和lock环境中,锁已经被别的线程拿走,等待获取锁。IO阻塞(Blocked),比如网络等待,文件打开,控制台读取。System.in.read()。
Java多线程的创建
线程驱动任务的处理,涉及到两个概念,线程(Thread)、以及任务(Task, Java里面叫Runnable),推荐的做法是写一个类(class)实现Runnable接口,作为Task,传给Thread的的构造函数,调用Thread的start方法即可。当然你也可以直接继承Thread类,重写(Override)run方法。从任务抽象的角度来说,推荐前者,后者的优势是简单。想创建多个线程,就new多个Thread并start即可。不过JVM有一个最大线程数的限制,-Xssjava命令行非标准可选项可以设置大小,不同平台可能不一样,需要查文档或写代码验证。
Java多线程的资源竞争(race condition)
多个线程同时访问同一份资源(内存块)的时候,可能发生某一个线程处理到一半,被别的线程抢走资源并修改的问题,造成资源的不一致。要避免此类问题,唯一的解决方案就是加锁。
Java有两种锁可供选择:
对象或者类(class)的锁。每一个对象或者类都有一个锁。使用synchronized关键字获取。 synchronized加到static方法上面就使用类锁,加到普通方法上面就用对象锁。除此之外synchronized还可以用于锁定关键区域块(Critical Section)。 synchronized之后要制定一个对象(锁的携带者),并把关键区域用大括号包裹起来。synchronized(this){// critical code}。显示构建的锁(java.util.concurrent.locks.Lock),调用lock的lock方法锁定关键代码。
原子性和可见性
原子性(Atomic)是指一个操作处理过程中,CPU(处理控制权) 不会被抢走。处理就会在一个时间片段内处理完毕。Java中两个double数字相加这样一条语句都不具有原子性。可见性(visibility)是指多个线程之间看到完全相同的资源,任何线程对资源的修改,其它线程都能够实时看到。synchronized能够保证资源的可见性 , volatile关键字也能够保证资源的可见性。
Java多线程之间的合作
Java多线程共享相同的内存,自然可以通过访问和修改同一份资源(对象)来交互和通信,只是需要注意线程的安全性。此外,Java还提供一些其它的线程交互方式,绝大多数都通过线程分派器(Thread Scheduler)完成。主要的一些交互方式如下:
提示线程分派器(Thread Scheduler)当前线程已经完成一个完整的工作,此时是一个切换到其它线程的好时机。通常这可以提高切换的可能,但是需要注意切换只是可能,不是一定发生。所以业务不能完全依赖于yield。Thread.yield()。
暂停当前线程的运行,等待另外一个线程运行完毕之后再继续运行。anotherThread.join()。这种依赖关系可以得到保证。依个人的理解,此时当前线程也是被阻塞(Blocked)了,等待另外一个线程运行完毕,然后唤醒。join会抛出InterruptedException(间接证明当前线程被Blocked),当前线程调用interrupt方法会解除依赖,并抛出InterruptedException。
wait notify notifyAll
这三个都是Object的方法,wait用来阻塞(Block)当前线程,但是会释放对象的锁,notify告诉线程分派器唤醒等待的某一个线程,notifyAll会唤醒所有等待的线程。需要特别注意的是,这三个方法都需要在锁定的环境(synchronized)里面使用,否则编译通过,但是运行报错。wait notify notifyAll 与 synchronized配对使用。
while(conditionIsNotSatisfied)
await signal signalAll
await signal signalAll是java.util.concurrent.locks.Condition的三个方法,Condition可以通过Lock获得Condition condition = lock.newCondition()。从功能上来说,await signal signalAll与wait notify notifyAll应该是相同的,只是await signal signalAll与Condition配对使用。
while(conditionIsNotSatisfied)
condition.await()
取消线程的运行
推荐的做法是调用线程的interrupt方法。线程也有stop函数,不过JDK1.5以后被弃用了,原因据说是因为会引起死锁。所有会抛出InterruptedException的线程阻塞都可以调用interrupt方法唤醒,不过是通过异常的方式退出,如果需要释放资源,一定要使用try finally语句。IO的阻塞、synchronized阻塞、lock阻塞都无法使用interrupt方法唤醒。IO阻塞可以通过关闭(close)资源的方式退出。如果是非阻塞的线程,需要检测interrupt状态,有两个方法,isInterrupted和interrupted,interrupted有副作用,会清理状态。标准的能够响应interrupt的程序格式大概如下:
public void run(){
// check interrupt status
while(!Thread.interrupted()){
// declare resource
// use resource
}catch(Exception e){
// release resource
}catch(InterruptedException ex){
// do something when InterruptedException triggered.
Java的线程是阻塞(同步)的,优点是顺序执行,易于编写和理解。有些场景下需要借助多线程才能提高运行效率。 最近两年非常火Node.js是非阻塞的,事件驱动,效率非常高,但是Node.js也非银弹,攻无不克,它是单线程的, 在手机都8核的年代里面,单线程会有CPU利用不足的问题。可能的一个解法是多进程,启动多个Node.js实例。 另外一个问题是代码的可读性会比较差一些,容易出现多次嵌套的结构。函数式编程,永远不会改变参数的值,没有全局变量,没有副作用,天然的是线程安全的。看起来像银弹,但是 函数式编程的门槛比较高,推广会是个问题。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:352364次
积分:4309
积分:4309
排名:第4816名
原创:70篇
评论:125条
阅读:5727
阅读:78592
(2)(2)(1)(3)(1)(4)(1)(4)(2)(2)(1)(4)(1)(2)(2)(2)(3)(7)(1)(12)(3)(2)(1)(1)(1)(11)3378人阅读
技术文章(50)
先来看一段单线程的原始代码(代码中有详细的注释):
&&&&&&&& 服务器(TCPServer.java):
import java.net.*;
import java.io.*;
public class TCPServer{
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(5566); //创建一个Socket服务器,监听5566端口
//利用死循环不停的监听端口
while(true){
Socket s = ss.accept(); //利用Socket服务器的accept()方法获取客户端Socket对象。
System.out.println(&第& + i +&个客户端成功连接!&);
DataInputStream dis = new DataInputStream(s.getInputStream()); //获取客户端Socket对象的输入流,并在外边加一层DataInputStream管道,目的是方便读取数据
System.out.println(dis.readUTF()); //读出流中的数据,DataInputStream对象的readUTF()方法可以读出流中的数据,而且支持中文
dis.close(); //关闭管道连接
s.close(); //关闭Socket连接
&&&&&&&&客户端(TCPClient.java):
import java.net.*;
import java.io.*;
public class TCPClient{
public static void main(String[] args) throws Exception{
Socket s = new Socket(&192.168.24.177&,5566); //创建一个Socket对象,连接IP地址为192.168.24.177的服务器的5566端口
DataOutputStream dos = new DataOutputStream(s.getOutputStream()); //获取Socket对象的输出流,并且在外边包一层DataOutputStream管道,方便输出数据
dos.writeUTF(&客户端消息&); //DataOutputStream对象的writeUTF()方法可以输出数据,并且支持中文
dos.flush(); //确保所有数据都已经输出
dos.close(); //关闭输出流
s.close(); //关闭Socket连接
&&&&&&&&以上代码利用Socket对象和ServerSocket对象进行简单的网络交互,即客户端通过DataOutputStream对象的writeUTF()方法向服务器发送消息,服务器利用DataInputStream对象的readUTF()方法读出数据。
&&&&&&&&看上去挺好,但ServerSocket对象的accept()方法是阻塞的方法,它会一直等待,直到有客户端连接。
&&&&&&&&同理,DataInputStream对象的readUTF()方法也是阻塞的方法,它也会一直等待,直到客户端调用writeUTF()方法。
&&&&&&&&因此,假如某个客户端成功连接服务器,但是迟迟不调用writeUTF()方法发送数据,服务器就要一直等待,直到客户端调用writeUTF()方法为止,此期间整个服务器是阻塞的,无法再接受其他客户端连接,显然这不符合实际情况。
&&&&&&&&要解决这个问题,当然要用多线程。
&&&&&&&&如果每个客户端都独有一个线程,让readUTF()方法阻塞客户端独有的线程,而不去阻塞服务器主线程,这样服务器就可以同时接受多个客户端连接,而不用考虑客户端何时调用writeUTF()方法发送数据。代码如下:
&&&&&&&&服务器(TCPServer.java):
import java.net.*;
import java.io.*;
public class TCPServer{
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(5566); //创建一个Socket服务器,监听5566端口
//利用死循环不停的监听端口
while(true){
Socket s = ss.accept();//利用Socket服务器的accept()方法获取客户端Socket对象。
System.out.println(&第& + i +&个客户端成功连接!&);
Client c = new Client(i,s); //创建客户端处理线程对象
Thread t =new Thread(c); //创建客户端处理线程
t.start(); //启动线程
//客户端处理线程类(实现Runnable接口)
class Client implements Runnable{
int clientIndex = 0; //保存客户端id
Socket s = //保存客户端Socket对象
Client(int i,Socket s){
clientIndex =
public void run(){
//打印出客户端数据
DataInputStream dis = new DataInputStream(s.getInputStream());
System.out.println(&第& + clientIndex + &个客户端发出消息:& + dis.readUTF());
dis.close();
s.close();
catch(Exception e)
&&&&&&&&客户端(TCPClient.java):
import java.net.*;
import java.io.*;
public class TCPClient{
public static void main(String[] args) throws Exception{
Socket s = new Socket(&192.168.24.177&,5566); //创建一个Socket对象,连接IP地址为192.168.24.177的服务器的5566端口
DataOutputStream dos = new DataOutputStream(s.getOutputStream()); //获取Socket对象的输出流,并且在外边包一层DataOutputStream管道,方便输出数据
Thread.sleep((int)(Math.random()*3000)); //让客户端不定时向服务器发送消息
dos.writeUTF(&客户端消息&); //DataOutputStream对象的writeUTF()方法可以输出数据,并且支持中文
dos.flush(); //确保所有数据都已经输出
dos.close(); //关闭输出流
s.close(); //关闭Socket连接
&&&&&&&&运行结果如下(参考结果,不一定相同!):
&&&&&&&&明显看出第2、3、4客户端都没有向服务器端发出消息,但都成功连接,而且第2、3、4客户端向服务器发出消息也没有顺序。
&&&&&&&&通过多线程,实现了多个客户端同时连接服务器,并且服务器能实时处理多个客户端发出的消息。
&&&&&&&&以上仅仅是作为初学者的一些想法,仅供参考!
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:320849次
积分:4459
积分:4459
排名:第4567名
原创:90篇
评论:350条
(1)(5)(6)(1)(8)(6)(6)(5)(5)(1)(1)(7)(4)(4)(4)(3)(25)java多线程在互联网公司的应用场景是什么_百度知道13889人阅读
Java学习笔记(16)
Java并发编程与技术内幕(9)
& & & &&&原创作品。转载请注明出处& & & & 写在前面的话:此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢?如果你觉得此文很简单,那推荐你看看Java并发包的的线程池(),或者看这个专栏:。你将会对Java里头的高并发场景下的线程有更加深刻的理解。目录& & & & 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前,首先让我们来了解下在操作系统中进程和线程的区别:  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)  线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。  多进程是指操作系统能同时运行多个任务(程序)。  多线程是指在同一程序中有多个顺序流在执行。在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用,此文这里不讲这个,有兴趣看这里&)一、扩展java.lang.Thread类这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。没有什么其它特殊的要求,那么可以使用Thread.(笔者推荐使用Runable,后头会说明为什么)。下面来看一个简单的实例package com.multithread.
*@functon 多线程学习
*@author 林炳文
class Thread1 extends Thread{
public Thread1(String name) {
this.name=
public void run() {
for (int i = 0; i & 5; i++) {
System.out.println(name + &运行
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
public class Main {
public static void main(String[] args) {
Thread1 mTh1=new Thread1(&A&);
Thread1 mTh2=new Thread1(&B&);
mTh1.start();
mTh2.start();
输出:A运行& :& 0B运行& :& 0A运行& :& 1A运行& :& 2A运行& :& 3A运行& :& 4B运行& :& 1B运行& :& 2B运行& :& 3B运行& :& 4再运行一下:A运行& :& 0B运行& :& 0B运行& :& 1B运行& :& 2B运行& :& 3B运行& :& 4A运行& :& 1A运行& :& 2A运行& :& 3A运行& :& 4说明:程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。&注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。但是start方法重复调用的话,会出现
Thread1 mTh1=new Thread1(&A&);
Thread1 mTh2=mTh1;
mTh1.start();
mTh2.start();输出:Exception in thread &main& java.lang.IllegalThreadStateException&& &at java.lang.Thread.start(Unknown Source)&& &at com.multithread.learning.Main.main(Main.java:31)A运行& :& 0A运行& :& 1A运行& :& 2A运行& :& 3A运行& :& 4二、实现java.lang.Runnable接口采用Runnable也是非常常见的一种,我们只需要重写run方法即可。下面也来看个实例。/**
*@functon 多线程学习
*@author 林炳文
package com.multithread.
class Thread2 implements Runnable{
public Thread2(String name) {
this.name=
public void run() {
for (int i = 0; i & 5; i++) {
System.out.println(name + &运行
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
public class Main {
public static void main(String[] args) {
new Thread(new Thread2(&C&)).start();
new Thread(new Thread2(&D&)).start();
输出:C运行& :& 0D运行& :& 0D运行& :& 1C运行& :& 1D运行& :& 2C运行& :& 2D运行& :& 3C运行& :& 3D运行& :& 4C运行& :& 4说明:Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。三、Thread和Runnable的区别如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。总结:实现Runnable接口比继承Thread类所具有的优势:1):适合多个相同的程序代码的线程去处理同一个资源2):可以避免java中的单继承的限制3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。&在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。四、线程状态转换下面的这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!1、新建状态(New):新创建了一个线程对象。2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。五、线程调度线程的调度1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。&Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:static int MAX_PRIORITY &&&&&&&&& 线程可以具有的最高优先级,取值为10。 static int MIN_PRIORITY &&&&&&&&& 线程可以具有的最低优先级,取值为1。 static int NORM_PRIORITY &&&&&&&&& 分配给线程的默认优先级,取值为5。 &Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。&每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。&2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。&3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。&4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。&5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。&6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。&注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。六、常用函数说明①sleep(long millis):&在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)②join():指等待t线程终止。使用方式。 join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。Thread t = new AThread(); t.start(); t.join(); 为什么要用join()方法 在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。 不加join。/**
*@functon 多线程学习,join
*@author 林炳文
package com.multithread.
class Thread1 extends Thread{
public Thread1(String name) {
super(name);
this.name=
public void run() {
System.out.println(Thread.currentThread().getName() + & 线程运行开始!&);
for (int i = 0; i & 5; i++) {
System.out.println(&子线程&+name + &运行 : & + i);
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + & 线程运行结束!&);
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+&主线程运行开始!&);
Thread1 mTh1=new Thread1(&A&);
Thread1 mTh2=new Thread1(&B&);
mTh1.start();
mTh2.start();
System.out.println(Thread.currentThread().getName()+ &主线程运行结束!&);
输出结果:main主线程运行开始!main主线程运行结束!B 线程运行开始!子线程B运行 : 0A 线程运行开始!子线程A运行 : 0子线程B运行 : 1子线程A运行 : 1子线程A运行 : 2子线程A运行 : 3子线程A运行 : 4A 线程运行结束!子线程B运行 : 2子线程B运行 : 3子线程B运行 : 4B 线程运行结束!发现主线程比子线程早结束加joinpublic class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+&主线程运行开始!&);
Thread1 mTh1=new Thread1(&A&);
Thread1 mTh2=new Thread1(&B&);
mTh1.start();
mTh2.start();
mTh1.join();
} catch (InterruptedException e) {
e.printStackTrace();
mTh2.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+ &主线程运行结束!&);
}运行结果:main主线程运行开始!A 线程运行开始!子线程A运行 : 0B 线程运行开始!子线程B运行 : 0子线程A运行 : 1子线程B运行 : 1子线程A运行 : 2子线程B运行 : 2子线程A运行 : 3子线程B运行 : 3子线程A运行 : 4子线程B运行 : 4A 线程运行结束!主线程一定会等子线程都结束了才结束③yield():暂停当前正在执行的线程对象,并执行其他线程。&&&&&&& Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。&&& & && yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。&结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。/**
*@functon 多线程学习 yield
*@author 林炳文
package com.multithread.
class ThreadYield extends Thread{
public ThreadYield(String name) {
super(name);
public void run() {
for (int i = 1; i &= 50; i++) {
System.out.println(&& + this.getName() + &-----& + i);
// 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i ==30) {
this.yield();
public class Main {
public static void main(String[] args) {
ThreadYield yt1 = new ThreadYield(&张三&);
ThreadYield yt2 = new ThreadYield(&李四&);
yt1.start();
yt2.start();
运行结果:第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。sleep()和yield()的区别&&&&&&& sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。&&&&&&& sleep&方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield&方法使当前线程让出&CPU&占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把&CPU&&的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程&&&&&& 另外,sleep&方法允许较低优先级的线程获得运行机会,但&yield()&&方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得&CPU&占有权。在一个运行系统中,如果较高优先级的线程没有调用&sleep&方法,又没有受到&I\O&阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。 ④setPriority():&更改线程的优先级。    MIN_PRIORITY = 1&   &&&NORM_PRIORITY = 5&&&&&&&&& &MAX_PRIORITY = 10用法:Thread4 t1 = new Thread4(&t1&);
Thread4 t2 = new Thread4(&t2&);
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);⑤interrupt():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!⑥wait()Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。&&& 单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:&&& 建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:/**
* wait用法
* @author DreamSea
package com.multithread.
public class MyThreadPrinter2 implements Runnable {
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name =
this.prev =
this.self =
public void run() {
int count = 10;
while (count & 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
self.notify();
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2(&A&, c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2(&B&, a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2(&C&, b, c);
new Thread(pa).start();
Thread.sleep(100);
//确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
输出结果:ABCABCABCABCABCABCABCABCABCABC&&&& 先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA-&ThreadB-&ThreadC-&ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。&&& wait和sleep区别共同点:
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。 2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态
,从而使线程立刻抛出InterruptedException。 && 如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 && 需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException
。 不同点:
1. Thread类的方法:sleep(),yield()等 && Object的方法:wait()和notify()等 2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 && sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常所以sleep()和wait()方法的最大区别是:    sleep()睡眠时,保持对象锁,仍然占有该锁;    而wait()睡眠时,释放对象锁。  但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。sleep()方法sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;   sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。   在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 wait()方法wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;  wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。  wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。七、常见线程名词解释主线程:JVM调用程序main()所产生的线程。当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。线程类的一些常用方法:&  sleep(): 强迫一个线程睡眠N毫秒。&  isAlive(): 判断一个线程是否存活。&  join(): 等待线程终止。&  activeCount(): 程序中活跃的线程数。&  enumerate(): 枚举程序中的线程。&&&& currentThread(): 得到当前线程。&  isDaemon(): 一个线程是否为守护线程。&  setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)&  setName(): 为线程设置一个名称。&  wait(): 强迫一个线程等待。&  notify(): 通知一个线程继续运行。&  setPriority(): 设置一个线程的优先级。八、线程同步1、synchronized关键字的作用域有二种: 1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象; 3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法; Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。在进一步阐述之前,我们需要明确几点:A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。B.每个对象只有一个锁(lock)与之相关联。C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。接着来讨论synchronized用到不同地方对代码产生的影响:&假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。&1.& 把synchronized当作函数修饰符时,示例代码如下:Public synchronized void methodAAA(){//….}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:public void methodAAA(){synchronized (this)&&&&& //& (1){&&&&&& //…..}}&(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(2.同步块,示例代码如下:&&&&&&&&&&& public void method3(SomeObject so)&&&&&&&&&&&&& {&&&&&&&&&&&&&&&&&&&& synchronized(so){&&&&&& //…..}}这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:class Foo implements Runnable{&&&&&& private byte[] lock = new byte[0];& // 特殊的instance变量&&& Public void methodA(){&&&&&& synchronized(lock) { //… }}//…..}注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。3.将synchronized作用于static 函数,示例代码如下:&&&&& Class Foo{public synchronized static void methodAAA()&& // 同步的static 函数{//….}public void methodBBB(){&&&&&& synchronized(Foo.class)&& //& class literal(类名称字面常量)}&&&&&& }&& 代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
九、线程数据传递在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。9.1、通过构造方法传递数据&在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:&
public class MyThread1 extends Thread
public MyThread1(String name)
this.name =
public void run()
System.out.println(&hello & + name);
public static void main(String[] args)
Thread thread = new MyThread1(&world&);
thread.start();
由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。&9.2、通过变量和方法传递数据&向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置 name变量:&
public class MyThread2 implements Runnable
public void setName(String name)
this.name =
public void run()
System.out.println(&hello & + name);
public static void main(String[] args)
MyThread2 myThread = new MyThread2();
myThread.setName(&world&);
Thread thread = new Thread(myThread);
thread.start();
9.3、通过回调函数传递数据&上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个 value是无法事先就传入线程类的。&
class Data
public int value = 0;
class Work
public void process(Data data, Integer numbers)
for (int n : numbers)
data.value +=
public class MyThread3 extends Thread
public MyThread3(Work work)
this.work =
public void run()
java.util.Random random = new java.util.Random();
Data data = new Data();
int n1 = random.nextInt(1000);
int n2 = random.nextInt(2000);
int n3 = random.nextInt(3000);
work.process(data, n1, n2, n3); // 使用回调函数
System.out.println(String.valueOf(n1) + &+& + String.valueOf(n2) + &+&
+ String.valueOf(n3) + &=& + data.value);
public static void main(String[] args)
Thread thread = new MyThread3(new Work());
thread.start();
} & 好了,Java多线程的基础知识就讲到这里了,有兴趣研究多线程的推荐直接看java的源码,你将会得到很大的提升!原创作品。转载请注明出处
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:680993次
积分:10003
积分:10003
排名:第1145名
原创:229篇
评论:918条
阅读:14889
阅读:28849
文章:13篇
阅读:28773
文章:18篇
阅读:48927
文章:23篇
阅读:171187
(4)(10)(10)(1)(6)(4)(6)(3)(12)(10)(11)(12)(14)(21)(28)(13)(16)(15)(4)(2)(13)(16)(2)(1)}

我要回帖

更多关于 java多线程使用场景 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信