一站式理解线程概念与应用

线程

进程:资源分配的最小单位
线程:程序执行的最小单位

线程开启

方法一:定义类继承Thread,重写run方法

优点:直接使用Thread类中的方法,代码简单

缺点:如果已经有了父类,就不能用这种方法

class MyThread extends Thread {								
    @Override
	public void run() {}										
}
MyThread mt = new MyThread();							
mt.start();

方法二:定义类实现Runnable接口,实现run方法

优点:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的

缺点:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

class MyRunnable implements Runnable {							
	@Override
	public void run() {}
}
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();	

方法三:实现Callable接口,重写call()

public static class CallableTest implements Callable<String>{
	public String call() throws Exception{
		return "Hello World!";
	}
}
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//启动线程
Future<String> future = threadPool.submit(new CallableTest());

线程关闭

线程任务结束,stop()暴力停止,interrupt()中断线程,产生异常停止(程序发生致命错误,或sleep()时间过长),return退出

功能

获取当前线程对象:Thread.currentThread()

获取当前线程对象名:Thread.currentThread().getName()

休眠线程:Thread.sleep(毫秒,纳秒)

设置为守护线程:myThread.setDaemon(true)

加入线程: join():当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续

join(int):可以等待指定的毫秒之后继续

礼让线程:yield()

优先级:setPriority()

Synchronized

同步代码块:使用synchronized关键字加上一个锁对象来定义一段代码
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

线程通信

时机:默认情况下CPU是随机切换线程的,希望线程有规律地执行
机制:等待:wait,唤醒:notify,唤醒所有:notifyAll

可重入锁:避免死锁;。如:synchronized和ReentrantLock
不可重入锁

互斥锁ReentrantLock类

在锁定代码段的时候,用lock加锁和unlock解锁,finally中unlock防死锁
Synchronized由jvm执行,出现异常,jvm自动释放;ReentrantLock由java代码实现,要保证锁一定被释放,必须将unlock放在finally中

同步:lock() unlock()

获取Condition对象:newCondition()

等待:await()

唤醒:signal()

临界资源和临界区

临界资源:每次仅允许一个进程访问的资源,进程间互斥访问

属于临界资源的硬件:打印机、磁带机

属于临界资源的软件:消息缓冲队列、缓冲区、数组、变量

临界区:每个进程中访问临界资源的代码。互斥访问决定每个进程在进入临界区之前,要检查临界资源是否占用

五大状态

新建状态:newThread®

就绪状态:start()

运行状态

阻塞状态:sleep()

结束状态

线程组ThreadGroup类1

ThreadGroup(String name)			//创建线程组对象并给其赋值名字
final ThreadGroup getThreadGroup()	//通过线程对象获取他所属于的组
final String getName()				//通过线程组对象获取他组的名字

默认情况下,所有的线程都属于主线程组

给线程设置分组:

1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
2,创建线程对象
3,Thread(ThreadGroup?group, Runnable?target, String?name)
4,设置整组的优先级或者守护线程

ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable mr = new MyRunnable();

// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, mr, "张三");	
Thread t2 = new Thread(tg, mr, "李四");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());	
tg.setDaemon(true);		//通过组名称设置后台线程,表示该组的线程都是后台线程

线程池

概述:程序启动新线程成功较高,因为涉及到操作系统交互。线程池可重用闲置线程,从而提高性能

Executors工厂类

//线程池继承关系
interface Executor
	interface ExecutorService
		abstract class AbstractExecutorService
			class ThreadPoolExecutor

使用步骤:

创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池

//单个线程的线程池
Executors.newSingleThreadExecutor();
//固定数量线程的线程池,达到线程的最大数量后,后面的线程排队等待
Executors.newFixedThreadPool(int nThreads);
//可缓存线程的线程池,线程池中的数量超过处理任务所需的线程数量,就会回收空闲线程(60s无执行)
Executors.newCachedThreadPool();
//无数量限制的线程池,支持定时和周期性执行线程
Executors.newScheduledThreadPool(int corePoolSize);

//上述四个方法返回ExecutorService对象
//使用线程池
mExecutorService.submit(
    new Runnable() {	//new MyRunnable()
        @Override public void run() {}});
//结束线程池
pool.shutdown();	

多线程程序实现的方式3

优点:可以返回值,可以抛出异常

缺点:代码复杂

ExecutorService pool = Executors.newFixedThreadPool(2);		// 创建线程池对象
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
Integer i1 = f1.get();	// V get()
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
pool.shutdown();	// 结束

public class MyCallable implements Callable<Integer> {
	private int number;
	public MyCallable(int number) {
		this.number = number;
	}
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int x = 1; x <= number; x++) {
			sum += x;
		}
		return sum;
	}	
}

四种线程池与BlockingQueue,命名为workQueue

b、newFixedThreadPool将corePoolSize和maximumPoolSize都置为n,使用LinkedBlockingQueue。
c、newCachedThreadPool将corePoolSize置为0,maximumPoolSize置为Integer.MAX_VALUE,使用SynchronousQueue
3、ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue
a、ArrayBlockingQueue:数组结构的有界队列,FIFO。构造函数必须传一个int参数指明ArrayBlockingQueue的大小
b、LinkedBlockingQueue:链表结构的无界队列,FIFO,newFixedThreadPool。可传int参数指明大小,反之Integer.MAX_VALUE。LinkedBlockingQueuemList = new LinkedBlockingQueue(100);
c、SynchronousQueue:不存储元素的阻塞队列,每次插入必须等待另一个线程调用移除操作,newCachedThreadPool使用。线程安全,阻塞的,不允许使用null。put一个元素之后,一直wait知道其他Thread把这个element取走
d、PriorityBlockingQueue:类似LinkedBlockingQueue,但不是FIFO,而是自然排序或者Comparator参数决定

进程与线程区别

	拥有独立的堆栈空间和数据段,每次应用程序启动时,须分配其独立的地址空间,建立众多的数据链表来维护它的代码段、堆栈段和数据段。因此进程崩溃后,不影响其他进程
	开销大,切换速度慢,效率低
	安全性高
	进程间通信相互独立,因此通信机制相对复杂。常用的如:管道,信号,消息队列,共享内存,套接字等
	可拥有多个线程且至少拥有一个线程
线程
	拥有独立的堆栈空间,但是共享数据段,线程间使用相同的地址空间,共享大部分数据。即进程的不同执行路径,一个线程崩溃后,进程也会崩溃
	开销小,切换速度快,效率高
	通信简单
	只能属于一个进程

进程与线程的选择

	无需频繁创建销毁对象
	应用于多机分布情况
	安全性要求高
线程
	需频繁创建销毁对象
	需大量计算,且要求性能,不影响主线程响应
	线程对CPU系统的效率高于进程,应用于多核分布情况
	并行操作
	效率要求高

猜你喜欢

转载自blog.csdn.net/mLuoya/article/details/87870447