日期:2014-05-20  浏览次数:20908 次

J2SE知识点归纳笔记(五)---Java多线程(一)

Java多线程:


前言:


当我们使用电脑时,可以一边听歌,一边和小学生打lol,一边用QQ和基友聊天;如果你够屌的话,还可以

用wps做实验报告呢= =;那么CPU怎么同时敢这么多事情呢?这就涉及到两个名词:多进程(Process)多线程(Thread)

Java语言的一个重要特点就是对多线程的支持,使得开发人员可以开发出同时处理多个任务的application,实现

任务的并发处理!

PS:因为多线程灰常的重要,所以这里分开两节讲,不让读者有太大的负担,这一节稍微简单一点

好了废话不多说!




相关概念:



线程的生命周期:





Java线程的创建:



代码示例:

以下两个代码实现的都是同一个功能;

完成计时的功能,每秒打印一行


继承Thread类:

package com.jay.example;


//自定义一个线程类继承Thread类,重写Thread中的run()方法
class MyThread extends Thread
{
	public MyThread() {
		//这里是标识进程名的,可以通过getName()获得进程的名称
		super("MyThread");
		System.out.println("新建一个线程:"+getName());
	}
	
	//重写的run()方法;通过循环打印1~10;
	//而每秒打印一行的功能是添加了sleep(1000); 1000毫秒 = 1秒
	//使用sleep需要对异常进行捕获
	public void run() {		
		for(int i = 1;i <= 3;i++)
		{
			System.out.println(Thread.currentThread().getName() +" : " + i );
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}


public class ThreadTest1 {
	public static void main(String[] args) {
		Thread t = Thread.currentThread();
		
		//新建一个MyThread的线程对象
		MyThread mt = new MyThread();
		//通过线程.start()方法启动线程中的run()方法
		mt.start();
		//这里是为了让线程运行完毕后才打印主线程中的语句
		//join():让异步执行的线程改成同步执行,直到这个线程退出
		//程序才会继续执行
		try {
			mt.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("打印完毕!");
	}
}


运行截图:





实现Runnable接口:

package com.jay.example;

//该代码实现了Runnable接口,重写了其中的run()方法
class MyThread2 implements Runnable
{
	public void run() {
		for(int i = 1;i <= 3;i++)
		{
			System.out.println(Thread.currentThread().getName() +" : " + i );
			//调用sleep方法需要对异常进行补货
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}		
	}	
}

public class ThreadTest2 {
	public static void main(String[] args) {
		//实例化自定义线程
		MyThread2 th = new MyThread2();
		//新建一个线程对象,把子线程对象作为参数传入,后面的参数是进程的name
		Thread td = new Thread(th,"MyThread2");		
		td.start();
		
	}
}


运行截图:




多线程的控制:

都说了是多线程,那么就是有多个线程同时运行,这就涉及到了多线程之间的控制了


多个线程的并发执行:

如果当前有多个线程在并发执行的话,多次运行的结果可能不是唯一的

Java对于线程启动后唯一能保证的就是每个线程都被启动并且结束,但对于哪个线程优先执行,何时执行都没有保证


实例代码:两个进程打印的先后顺序

package com.jay.example;

/*
 * 该程序演示的是线程的并发执行;
 * 我们定义一个MyThread1类实现Runnable接口
 * 重写run()方法,在该方法中打印区分哪个线程的语句
 * 在main方法中实例化两个MyThread1对象并且作为参数
 * 传递给Thread对象,start()方法调用,输出结果!
 * */

class MyThread1 implements Runnable
{
	public void run() {
		for(int i=0;i<10;i++)
		{
			System.out.println(Thread.currentThread().getName());
		}
	}
}

public class ThreadTest1 {
	
	public static void main(String[] args) {
		
		MyThread1 my1 = new MyThread1();
		MyThread1 my2 = new MyThread1();
		
		Thread t1 = new Thread(my1,"第一个线程被调用!");
		Thread t2 = new Thread(my2,"第二个线程被调用!");
		
		t1.start();
		t2.start();
	}	
}


运行截图:


结果分析:

运行后发现,线程是交替交替运行的,也说明了Java多线程的特点;

而这里又涉及到另一个概念了,就是线程的优先级



线程的优先级:

操作系统中的线程是具有优先级的,Java运行环境采用的是固定优先级调度算法(Fixed Priority Scheduling)

某一时刻有多个线程在运行,JVM会选择优先级最高的线程运行,优先级较高的线程会抢占cpu时间,

相同优先级的线程可能顺序执行,也可能分时执行,取决于本地操作系统的线程调度策略

虽然说在任意时刻,应该是最高优先级的线程在运行,但是这样是不能保证的,调度程序有可能选择

优先级较低的线程以避免饥饿(starvation)


抢占(pre-empt)策略:当一个优先级更高的进程进行可运行状态时发生抢占,终止当正在运行的进程而

立即去执行优先级更高 的线程,而两个相同优先级的线程则采用循环执行策略(round-robin);


饥饿(starvation):当等待时间给进程的推进和响应带来明显的影响时就称发生了进程饥饿

饥饿到一定程度的进程所赋予的任务即使完成,也已经不再具有实际意义,该进程就饿死了!


 如:一个打印机文件打印的问题,采用短文件有限的策略,如果短文件太多,

 那么长文件一直无限期的推迟,那么还打印个毛啊!




在Java中,优先级从,0~10,整数数值越大,优先级越大;默认线程优先级为5;可以通过setPriority()方法改变线程的优先级;

通过getPriority()获得线程的优先级;注意Thread类优先级的几个字段:

MIN_PRIORITY(最低优先级1)     MAX_PRIORITY(最高优先级10)     NORM_PRIORITY(默认优先级5)

只能说优先级高的线程有更大的可能性获得CPU,但这并不能说优先级较低的线程就永远最后执行!!

   

代码示例:

package com.jay.example;

/*
 * 该代码演示的是线程优先级的使用
 * 定义了三个线程,重新设置了优先级
 * 在调用start()方法输出哪个线程被调用了
 * */

class MyThread1 implements Runnable
{
	public void run() {
		for(int i=0;i<10;i++)
		{
			System.out.println(Thread.currentThread().getName());
		}
	}
}

public class ThreadTest1 {
	
	public static void main(String[] args) {
		
		MyThread1 my1 = new MyThread1();
		MyThread1 my2 = new MyThread1();
		MyThread1 my3 = new MyThread1();
		
		Thread t1 = new Thread(my1,"第一个线程被调用!");
		Thread t2 = new Thread(my2,"第二个线程被调用!");
		Thread t3 = new Thread(my2,"第三个线程被调用!");
		
		t1.setPriority(Thread.MIN_PRIORITY);
		t2.setPriority(Thread.MAX_PRIORITY);
		t3.setPriority(Thread.NORM_PRIORITY);
		
		System.out.println("设置后的线程优先级依次是:" + t1.getPriority() + "   " + t2.getPriority() + "  "+ t3.getPriority());
		
		t1.start();
		t2.start();
		t3.start();
	}	
}

运行截图:



结果分析:

进程优先级最低的第一个执行?这是不是有点意外,其实不然,只要读者运行几次会发现打印结果都是不一样的;

好像我们设置了优先级没什么用,对吧?也不能说没用,只是给系统提供一个参考吧,让他优先考虑分配CPU时间给该线程

除了优先级,操作系统还要考虑其他的一些东东哦!当然,我们在开发中肯定要有方法来规定线程的执行的!那就是线程调度的一些方法!



线程调度:

按照前面的说法,线程的运行是没有保证的,那么我们还怎么玩多线程;显然是不可能的;

Thread类给我们提供了一些用于实现线程调度的方法


sleep():

让线程睡一会儿,参数是毫秒级,使用该方法时需要捕获InterruptedException异常

eg:Thread.sleep(1000);  //让当前线程休眠1秒


yield():

暂停当前线程,运行其他线程,即让出CPU运行时间,进入就绪状态,从而让其他的进程获得CPU时间执行

不过对于抢占式操作系统没有什么意义,或者说没效果


join():

想让一个线程等待另一个线程执行完毕后再继续执行的情况下使用

可以添加参数,long类型的毫秒,等待该线程终止的时间最长为多少毫秒

可以参考第一个例子,比如m.join,那么等m进程执行完毕后才能执行其他程序这样!





锁与线程同步:

当有两个或以上线程操作同一时刻并发地访问同一资源,可能会带来一些问题;

比如比较经典的生产者和消费者的问题,如果生产者还没生产,那么消费者就进行消费了,那么这样肯定是错误的!

所以这里要引入一个锁的概念:就是对共享的资源加以限制,让该资源同一时间仅仅只能够有一个线程对他进行访问




使用实例:

定义一个资源类,在里面实现两个同步方法,再对其进行访问

代码:

package com.jay.example;

/*
 * 该代码演示的是同步方法与同步块的使用
 * 定义一个资源类,分别用同步方法和同步代码块定义两个同步的方法
 * 然后定义一个线程类,在线程类中实现对该资源的访问
 * 因为加了同步锁,所以同一时间只允许一个进程访问
 * 实例化两个自定义线程对象,作为传给两个Thread对象
 * 调用start()方法,启动自定义线程的run()方法,完成打印输出
 * */

class Source
{
	//定义同步方法
	synchronized void method1()
	{
		System.out.println("进入方法一,获得锁");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("方法一执行完毕,释放锁");
	}
	
	
	//在该方法中使用同步块
	void method2()
	{
		synchronized(this)
		{
			System.out.println("进入方法二,获得锁");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("方法二执行完毕,释放锁");
		}
	}
	
}

//定义一个操作资源的线程类:
class MyThread implements Runnable
{	
	private String name;
	Source s = null;
	
	MyThread(Source s,String name)
	{
		this.s = s;
		this.name = name;
	}
	
	
	public void run() {
		if(name.equals("method1"))s.method1();
		else 
		{System.out.println("方法二启动,准备调用第二个方法");s.method2();}
	}
}

public class ThreadTest2 {
	public static void main(String[] args) {
		//实例化一个资源对象传入到线程类中
		Source s = new Source();
		//创建两个MyThread对象
		MyThread mt1 = new MyThread(s, "method1");
		MyThread mt2 = new MyThread(s, "method2");
		
		//自定义线程对象传入Thread对象中
		Thread t1 = new Thread(mt1);
		Thread t2 = new Thread(mt2);
		
		//调用start方法启动两个线程
		t1.start();
		t2.start();
	}
}

运行截图:




线程死锁问题:

简单的说就说,两个线程都在等待对方拥有的资源,就是A等B的资源,B也在等A的资源,

我不给你你不给我,就一直在那里纠结,一直无限的等下去这个就是死锁了


一个简单的死锁例子:

该代码演示的是定义了一个资源类,然后自定义了一个线程类,该类在run方法中使用了两个synchronized嵌套;

就是每个线程对象需要获得两个资源才可以运行;为了方便演示,我故意实例化了三个资源对象和自定义线程对象

一号线程需要得到1,2资源,二号线程需要得到2,3资源,三号线程需要得到3,1资源

然后每个线程都获得了对应的一个资源,一直在等另一个资源;一等二的2资源,二等三的3资源,三等一的1资源

就这样一直僵持,就形成了死锁


代码演示:

package com.jay.example;
/*
 * 该代码演示的是进程的死锁
 * 定义了一个资源类,在自定义线程的run()方法中
 * 使用了synchronized的嵌套,实现一个线程需要两个资源才可以运行
 * 在main()方法中依次实例化了三个:资源对象,自定义线程对象
 * 让1线程需要1.2号资源;让2线程需要2.3号资源;让1线程需要3.1号资源
 * 结果就互相等呗,就形成了死锁
 * */


//定义资源类
class Resource
{
	String name;
	public Resource(String name) {
		this.name = name;
	}
}

//自定义线程类
class MyThread2 implements Runnable
{
	Resource res1 = null;
	Resource res2 = null;
	String name;
	
	//构造方法的重写
	public MyThread2(Resource res1,Resource res2,String name) {
		this.res1 = res1;
		this.res2 = res2;
		this.name = name;
	}
	
	public void run() {
		//外层synchronized块
		synchronized (res1) {
			System.out.println(this.name + " 获得了 "+ res1.name);
			try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();};
			System.out.println(this.name + "等待 "+res2.name + "释放资源!");
			//里层syncronized块
			synchronized (res2) {
					System.out.println(this.name + "获得 了 " +res2.name);
					try{Thread.sleep(1000);}catch(InterruptedException ex){ex.printStackTrace();};
				}
			}
		}
	}


public class ThreadTest3 {
	public static void main(String[] args) {
		//创建资源对象
		Resource res1 = new Resource("资源一");
		Resource res2 = new Resource("资源二");
		Resource res3 = new Resource("资源三");
		
		//实例化三个自定义线程对象,并且把他们传给Thread对象
		MyThread2 mt1 = new MyThread2(res1,res2,  "线程一");
		MyThread2 mt2 = new MyThread2(res2,res3,  "线程二");
		MyThread2 mt3 = new MyThread2(res3,res1,  "线程三");
		
		Thread t1 = new Thread(mt1);
		Thread t2 = new Thread(mt2);
		Thread t3 = new Thread(mt3);
		
		t1.start();
		t2.start();
		t3.start();
	}
}


运行截图:



结果分析:

如图,就这样卡住了,但是显示程序还在运行,就这样一直在那里等啊等

这样的程序是已经死掉的没有意义的程序了,所以在编写代码的时候要

预防死锁!这就是操作系统的概念了,有兴趣的可以看下参考系统的书籍



总结:

在这一节中,我们了解了多线程的相关概念,生命周期;

以及创建线程的两个方法,多个线程的并发执行,线程的优先级

线程调度的三个常用方法;锁与线程同步的问题等

大家回去要自己动手写下代码理解下,有什么问题可以写代码测试一下



ps:清明因为回家祭祖,就没更新了,今天又继续拉,

如果对本文有什么疑问,意见,纰漏,建议的话,

欢迎指出,感激万分,谢谢!O(∩_∩)O!