码迷,mamicode.com
首页 > 编程语言 > 详细

Java多线程 探险

时间:2016-05-13 02:52:43      阅读:216      评论:0      收藏:0      [点我收藏+]

标签:

当一个程序进入内存运行时,即变成一个进程,进程特征:独立性;动态性;并发性。线程也被称为轻量级进程,线程是进程的执行单元,线程在程序中是独立的、并发的执行流,线程可以拥有自己的堆栈、程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。线程的执行是抢占式的,线程比进程拥有更高的性能。


1)区别并发性和并行性:

并发性:同一时刻只有一条指令执行,多个进程指令被快速轮换执行。

并行性:在同一时刻有多条指令在多个处理器上同时执行。


2)使用多线程编程的好处:

进程之间不能共享内存,但线程之间可以;使用多线程来实现多任务并发比多进程的效率高;Java内置了多线程功能支持。

技术分享


1.创建线程


1)通过继承Thread类来创建并启动多线程

public class MyThread extends Thread {
	
	private int i ;
	// 重写run方法,run方法的方法体就是线程执行体
	public void run() {
		for ( ; i < 100 ; i++ ) {
			// 当线程类继承Thread类时,直接使用this即可获取当前线程
			// Thread对象的getName()返回当前该线程的名字
			System.out.println(getName() +  " " + i);
		}
	}
	
	public static void main(String[] args) {
		for (int i = 0; i < 100;  i++) {
			// 调用Thread的currentThread方法获取当前线程
			System.out.println(Thread.currentThread().getName() +  " " + i);
			if (i == 20) {
				new MyThread().start(); // 创建并启动第一条线程
				new MyThread().start(); // 创建并启动第二条线程
			}
		}
	}
}

2)实现Runnable接口创建线程类

public class MyThread2 implements Runnable {
	
	private int i ;
	// run方法同样是线程执行体
	public void run() {
		for ( ; i < 100 ; i++ ) {
			// 当线程类实现Runnable接口时,如果想获取当前线程,只能用Thread.currentThread()方法
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 100;  i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			if (i == 20) {
				MyThread2 st = new MyThread2(); 
				// 通过new Thread(target , name)方法创建新线程
				new Thread(st , "新线程1").start();
				new Thread(st , "新线程2").start();
			}
		}
	}
}

3)实现Callable接口来实现线程类

public class MyThread3 {
	
	public static void main(String[] args) {
		// 创建Callable对象
		MyThread3 rt = new MyThread3();
		// 先使用Lambda表达式创建Callable<Integer>对象
		// 使用FutureTask来包装Callable对象
		FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
			int i = 0;
			for ( ; i < 100 ; i++ ) {
				System.out.println(Thread.currentThread().getName()	+ "的循环变量i的值:" + i);
			}
			// call()方法可以有返回值
			return i;
		});
		for (int i = 0 ; i < 100 ; i++) {
			System.out.println(Thread.currentThread().getName() + "的循环变量i的值:" + i);
			if (i == 20) {
				// 实质还是以Callable对象来创建、并启动线程
				new Thread(task , "有返回值的线程").start();
			}
		} try {
			// 获取线程返回值
			System.out.println("子线程的返回值:" + task.get());
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}

注意:启动线程的时候如果直接调用run方法,则run方法立即执行,而且在run方法返回之前其他线程无法并发执行,变成了单线程程序。



2.创建线程的三种方式对比


1)继承Thread类来创建多线程

优点:编写简单,访问当前线程简单,直接使用this即可。

缺点:不能继承其他父类


2)实现Runnable接口或Callable接口来创建多线程

优点:共享一个target对象,适合多个线程来处理一个资源的情况。

缺点:编程相对复杂,访问当前线程必须使用Thread.currentThread()方法。


故一般推荐采用实现Runnable接口或Callable接口来创建多线程



3.线程的生命周期


线程状态转换图如下:
技术分享
注意:
1)如果程序调用子线程的start()方法后子线程立即执行,程序可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠1毫秒。
2)直接掉用线程的stop方法来结束该线程,容易导致死锁,通常不推荐使用。
3)测试线程是否死亡可以用isAlive()方法,当线程处于就绪、运行、阻塞三种状态时,该方法返回true;当线程处于新建、死亡两种状态时返回false。
4)不要对处于死亡的线程调用start()方法,否则会引发IllegalThreadStateException异常。


4.控制线程


1)join()
让一个线程等待另一个线程完成的方法

2)后台线程
特征:如果前台线程都死亡,后台线程都死亡。
调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。
isDaemon()方法用于判断指定线程是否为后台线程,主线程默认为前台线程。

3)sleep
Thread类的静态方法sleep(long millis);
用来暂停程序的执行,暂停millis毫秒,并进入阻塞状态。

4)线程让步:yield
暂停线程,不会阻塞线程,只是将线程转入就绪状态

5)改变线程优先级
设置成最高优先级:.setPriority(Thread.MAX_PRIORITY)
设置成最低优先级:.setPriority(Thread.MIN_PRIORITY)
改变主线程的优先级:Thread.currentThread().setPriority(6);


5.线程同步


解决线程安全问题

1)同步代码块
语法:
synchronized (obj) { // obj:同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定
	// 同步代码块
}
通常推荐使用可能被并发访问的共享资源充当同步监视器
public class DrawThread extends Thread {
	
	...
	public void run() {
		// 使用account作为同步监视器,任何线程进入下面同步代码块之前,
		// 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
		// 这种做法符合:“加锁 → 修改 → 释放锁”的逻辑
		synchronized (obj) {
			...
		}
		// 同步代码块结束,该线程释放同步锁
	}
	...
}

2)同步方法

同步方法就是使用synchronized关键字来修饰某个方法(synchronized关键字可以修改方法、代码块,但是不能修饰构造器、成员变量)。

public class Account {
	
	...
	// 提供一个线程安全draw()方法来完成操作
	public synchronized void draw(double drawAmount) {
		...
	}
	...
}
拓展:

可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采用如下策略:

a)不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法同步。

b)如果可变类有两种运行环境(单线程和多线程环境),则应该为该可变类提供线程不安全版本(单线程环境中使用线程不安全版本以保证性能)和线程安全版本。

3)释放同步监视器的锁定

a)线程会在如下几种情况释放对同步监视器的锁定:

同步方法、同步代码块执行结束;

同步方法、同步代码块中遇到break、return终止了执行;

出现了未处理的Error或Exception;

执行了同步监视器的wait()方法,则当前线程暂停并释放同步监视器。

b)如下几种情况线程不会释放对同步监视器的锁定:

调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行;

其他线程调用了该线程的suspend()方法将该线程挂起。


4)同步锁(Lock)

Lock(Java5新特性)提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。


5)死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁


Java多线程 探险

标签:

原文地址:http://blog.csdn.net/smartbetter/article/details/51335165

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!