在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多
新建 New — 就绪 Runnable — 运行 Running — 阻塞 Blocked — 死亡 Dead
新建 New:就是刚使用new方法,new出来的线程;
就绪 Runnable:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
运行 Running:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
阻塞 Blocked:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
死亡 Dead:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
1.等待I/O流的输入输出
2.等待网络资源,即网速问题
3.调用sleep()方法,需要等sleep时间结束
4.调用wait()方法,需要调用notify()唤醒线程
5.其他线程执行join()方法,当前线程则会阻塞,需要等其他线程执行完。
1.线程正常完成工作
2.调用stop()方法,强行停止线程
3.外部原因中断线程
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程;
代码示例:
public class ThreadDemo extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("run: 我在吃饭====" + i);
}
}
//main
public static void main(String[] args) {
//创建一个线程对象
ThreadDemo testThread = new ThreadDemo();
//调用start()开启线程
testThread.start();
for (int i = 0; i < 50; i++) {
System.out.println("main:我在看电影====" + i);
}
}
}
执行结果:
可以看出,main()方法中的程序和run()方法中的程序交替执行
此时,我们将statr()方法,改成run()方法,再看结果:
可以看出,run()方法执行完之后,main()方法才开始执行:
推荐使用Runnable对象,因为Java单继承的局限性
自定义线程类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动对象;
代码示例:
public class RunnableDemo implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("run: 我在吃饭====" + i);
}
}
public static void main(String[] args) {
//创建一个线程对象
ThreadDemo testThread = new ThreadDemo();
//调用start()开启线程
testThread.start();
for (int i = 0; i < 50; i++) {
System.out.println("main:我在看电影====" + i);
}
}
}
可以定义返回值
可以抛出异常
实现Callable有两种:
1. 使用线程池:
代码示例:
public class CallableDemo implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("run: 我在吃饭====" + i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo callableDemo = new CallableDemo();
//1.创建执行服务
ExecutorService es = Executors.newFixedThreadPool(1);
//2.执行提交
Future<Boolean> r1 = es.submit(callableDemo);
//3.获取结果
Boolean res = r1.get();
System.out.println(res);
//4,关闭服务
es.shutdown();
}
}
执行结果:
2. 使用FutureTask包装:
代码示例:
public class CallableDemo2 implements Callable {
@Override
public Object call() throws Exception {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("run: 我在吃饭====" + i);
}
return 666;
}
public static void main(String[] args) {
CallableDemo2 testCallable = new CallableDemo2();
//创建多个FutureTask对象,才能多次执行线程
FutureTask futureTask1 = new FutureTask(testCallable);
FutureTask futureTask2 = new FutureTask(testCallable);
new Thread(futureTask1).start();
new Thread(futureTask2).start();
try {
System.out.println(futureTask1.get());
System.out.println(futureTask2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
执行结果:
剩余待完善。。。
wait()和 sleep()的区别:
线程休眠 线程礼让
join()的使用
线程的优先级
用户线程和守护线程
线程同步(线程锁 synchronized)
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,但同时也会存在一些问题:
同步块:sunchronized(Obj){}
Obj称之为 同步监视器
CopyOnWriteArrayList线程安全的集合
概念:
多个线程各自占有一些资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止的情形,叫做死锁。某一个同步块同时拥有"两个以上对象的锁"时,就可能发生"死锁"的问题。
产生死锁的四个必要条件:
只要想办法破掉以上的一个或者多个就可以避免死锁的产生!
Lock锁
ReentrantLock 可重入锁
synchronized 与 Lock 的对比:
生产者和消费者问题