线程小总结
在计算机中,单核计算机只能存在一个管程,管程是由进程组成的,所谓的进程,是在计算机中运行的软件或程序,如你正在使用的浏览器,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()方法的对象一致。
上述的程序采用了生产消费模型,通过快递店联结消费者与网店,趋向代偶合的目的。
相关推荐
java多线程总结
在论坛上面常常看到初学者对线程的无可奈何,所以总结出了下面一篇文章,希望对一些正在学习使用java线程的初学者有所帮助。 首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多...
在多线程编程这块,我们经常要使用...HandlerThread顾名思义就是可以处理消息循环的线程,他是一个拥有Looper的线程,可以处理消息循环。 与其说Handler和一个线程绑定,不如说Handler是和Looper一一对应的。 Handl
不错的线程总结(全面上)1
关于Java编程中多线程的总结,是很难得的一个具有总结性的文档。
最近定位了在一个多线程服务器程序...整个定位过程遇到的问题和解决办法对于多线程内存越界问题都很典型,简单总结一下和大家分享。只对终极组合秘技感兴趣的同学,请直接阅读最后一节,其他的章节写到这里是为了科普。
java多线程编程大总结:Java 线程是 Java 语言中一个非常重要的部分,Java5 之前,多线程的语言支持还是比较弱的, 内容也较少,写一个复杂的多线程程序是相当有挑战性的。 在Java5 以后,Java 对多线程做了很多...
1、单线程加锁读写全局数据,每秒读多少次?建议使用读XXX万次,用多少时间来计算。 2、多线程加锁读写全局数据,伴随着线程增加(冲突增加),每秒读多少次? 2.1、线程增加到多少时读写次数趋于平滑,即不再增加...
花费了一上午的时候 写了一些demo。认识到四种线程池的区别。上传到csdn 供以后学习
多线程的常见问题总结分析 1、多线程技术介绍 2、主线程介绍 3、创建线程的方式 4、线程中的异常问题 5、线程运行状态 6、线程第二种创建方式 7、多线程练习 8、线程安全问题分析和解决 9、多线程细节 10、同步使用...
线程是一个独立的运行单元,每个进程内部都有多个线程,每个线程都可以各自同时执行指令。每个线程都有自己独立的栈,但是与进程内的其他线程共享内存。但是对于.NET的客户端程序(Console,WPF,WinForms)是由CLR...
第一章 线程简介 Java术语 线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口的线程 线程的生命周期 线程命名 访问线程 线程的启动、停止和连接 总结 第三章 同步技术...
包括线程的介绍,与进程的区别,多线程与单线程,怎样实现多线程。用户级线程的优缺点。
使用同步器作为多线程中级教材,主要是对上一套初级教程做一个简单的总结, 并且对接下来脚本的线程处理,及监控线程起到一个较高的实际认知。 对多线程基 础及后续多线程课程有承前启后的作用 主要学习内容: 1....
在使用线程时,最麻烦的就是线程的同步控制,如... 本人经过试验和总结,整理出可以安全的进行暂停、继续、停止线程执行的一个线程类,能非常方便的对线程进行控制。 附件里是源码和Sample程序,并有详细的使用说明。
C#.net同步异步SOCKET通讯和多线程总结 同步套接字通信 Socket支持下的网上点对点的通信 服务端实现监听连接,客户端实现发送连接请求,建立连接后进行发送和接收数据的功能 服务器端建立一个socket,设置好本机的ip...
主要分析线程局部存储机制的实现,稍微底层了一点点
对于一个进程中的多个线程来说,多个线程共享进程的内存块,当有新的线程产生的时候,操作系统不分配新的内存,而是让新线程共享原有的进程块的内存。因此,线程间的通信很容易,速度也很快。不同的进程因为处于不同...
创建线程有两种办法,一种继承QThread重载run, 一种继承QObject用moveToThread,详细代码比较
2020面试题总结多线程篇.pdf