简介
世间万物都可以同时完成很多工作,例如,人体可以同时进行呼吸、血液循环等活动,用户既可以使用计算机听歌,也可以使用它打印文件,而这些活动完全同时进行,这种思想放在Java中被称为并发,而将并发完成的每一件事情称为线程。
在java 中,并不是所有的程序语言都支持线程,在以往的程序中,多以一个任务完后在进行下一个项目的模式进行开发,这样下一个任务的开始必须等待前一个任务的结束。Java语言提供了并发机制,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程。
Java 的多线程在每个操作系统中的运行方式也存在差异,Windows操作系统是多任务的操作系统,它以进程为单位。一个进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。程序可以分配给每个进程一段有限的使用cpu的时间(也可以成为cpu时间片),cpu在这段时间中执行某个进程,然后下一个时间片又跳至另一个进程中去执行,由于cpu转换较快,所以使得每个进程好像是同时执行一样。
一个线程则是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发程序执行的线程,在单线程中,程序代码按调用顺序依次往下执行,如果需要一个进程同时完成多段代码的操作,就需要产生多线程。
进程定义
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程实例
public class 进程示例 {
//java中不同进程之间相互访问是很难的,而通过文件,或者网络可以进行访问
//由于不同进程所分配的内存不同,所以才会出现不可相互访问,但是线程弥补了这种缺点。
//操作系统管理该进程
public static void main (String[] args) {
int i=0;
for (; ; ){
System.out.println (i++);
}
}
}
实现线程的两种方式
在Java中提供两种两种方式实现线程,分别为继承java.lang.Thread与实现java.lang.Runnable接口。
package 线程;
class T extends Thread{
private Integer i=null;
public T(Integer i) {//构造
this.i=i;
}
public void run() {
for(;;) {
System.out.println(i++);
}
}
}
public class 线程2{
public static void main(String[] args) {
Integer i=0;
T t=new T(i);
T t1=new T(i);
// t.run();不能使用此方法,因为不能实现分时
// t1.run();
t.start();
t1.start();//执行出来是乱码,因为是同时进行的,不能实现分时
}
}
package 线程;
/**
* 在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口
*Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,
* 但是一个类只能继承一个父类,这是此方法的局限。
*在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成。
* 但是在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:public Thread(Runnable targer)此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。
* (start()可以协调系统的资源):
*在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
* 避免点继承的局限,一个类可以继承多个接口。
* 适合于资源的共享
*
*
*
*
*/
//创建线程的第二种方法
//Runnable是接口,Thread是它的实现类
class T1 implements Runnable{
private Integer i=null;
public T1(Integer i) {
this.i=i;
}
//重写run()方法
public void run() {
for(;;) {
System.out.println(i++);
}
}
}
public class 线程 {
public static void main(String[] args) {
Integer i=0;
T1 t=new T1(i);
T1 t1=new T1(i);
Thread tt=new Thread(t);
tt.start();
Thread tt1=new Thread(t1);
tt1.start();
}
}
Thread类:
开启线程:对象.start()
Runnable接口:
开启线程:
T1 t=new T1(i);
T1 t1=new T1(i);
Thread tt=new Thread(t);
tt.start();
线程死锁的原理及解决方式
多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
先看生活中的一个实例,2个人一起吃饭但是只有一双筷子,2人轮流吃(同时拥有2只筷子才能吃)。某一个时候,一个拿了左筷子,一人拿了右筷子,2个人都同时占用一个资源,等待另一个资源,这个时候甲在等待乙吃完并释放它占有的筷子,同理,乙也在等待甲吃完并释放它占有的筷子,这样就陷入了一个死循环,谁也无法继续吃饭。。。
在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
Java中活锁和死锁有什么区别?
死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。死锁发生的四个条件
1、互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
2、请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
3、不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。
4、环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即:{p0,p1,p2,...pn},进程p0(或线程)等待p1占用的资源,p1等待p2占用的资源,pn等待p0占用的资源。(最直观的理解是,p0等待p1占用的资源,而p1而在等待p0占用的资源,于是两个进程就相互等待)
活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。
死锁例子
package 线程;
class ttt extends Thread {
private Integer p1=null;
private Integer p2=null;
private String name=null;
public ttt(Integer p1,Integer p2,String name){
this.p1=p1;
this.p2=p2;
this.name=name;
}
public void run(){
if("张三".equals(this.name))
synchronized (p1){
try{
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (p2){
}
}else {
synchronized (p2) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (p1) {
}
}
}
}
}
//因为交叉访问对方的一些资源而出现死锁的现象,所以尽量避免交叉访问
public class 死锁 {
public static void main(String[] args){
Integer p1=10;
Integer p2=11;
ttt ttt=new ttt(p1,p2,"张三");
ttt bbb=new ttt(p1,p2,"李四");
ttt.start();
bbb.start();
}
}
运行结果:
线程同步以及多线程竞争不同资源而出现的解决方案
此篇博文主要学习线程中的互斥锁
对象互斥锁的用法
1、给可能竞争的资源对象加上互斥锁,synchronized (this),锁定当前对象。
while(true){ synchronized (p) {//加锁,线程数,避免程序执行一半去执行别的线程 if (this.p.pack <= 0) { // break; p.notify(); p.wait(); } else { System.out.println(this.name + "吃第" + this.p.pack + "个包子"); --this.p.pack; // p.eat(this.name); } }
2、给竞争的方法加上锁
public synchronized void eat(String name) { System.out.println(name + "吃第" + this.p.pack + "个包子"); --this.p.pack; }