线程
进程:资源分配的最小单位
线程:程序执行的最小单位
线程开启
方法一:定义类继承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系统的效率高于进程,应用于多核分布情况
并行操作
效率要求高