- 并发:指两个或多个事件在同一个时间段发生,交替执行
- 并行:指两个或多个事件在同一时刻发生
进程:指一个内存中运行的程序,每一个进程都有一个独立的内存空间,一个应用程序可以运行多个进程,进程也是程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序就是一个进程从创建,运行到消亡的过程。
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,如果有多个线程,当前程序就叫多线程程序
理解:应用程序=软件 进程=功能模块 线程=方法
多线程的好处:
1 效率高
2 多个线程之间互不干扰
线程调度
1 分时调度:所有线程轮流使用CPU的使用权,平均分配CPU的使用时间
2 抢占式调度:优先级高的先调用,相同优先级则随机调用
Java使用的是抢占式调度
main()就是主线程
创建线程
1 继承Tread类
2 实现Runable接口
2 实现Runable接口比继承Tread类更加具有优势
1 多态,可维护性
不同点:继承Tread类的线程类本身是一个线程类的子类,所以可以继承Tread类的start()来开启线程,而Runable接口的实现类并不是Tread类,所以并不可以使用start()来开启线程,需要Tread类的实例对象并且传入Runable接口的实现类对象来开启线程。
简化代码:使用匿名内部类
3 实现Callable接口
1 可以有返回值
2 可以抛出异常
public class MyCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
for (int j = 0; j < 200; j++) {
System.out.println("线程名称"+Thread.currentThread().getName()+"执行"+j+"步");
}
TestCallable t1 = new TestCallable();
TestCallable t2 = new TestCallable();
TestCallable t3 = new TestCallable();
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
//提交执行
Future<Integer> submit1 = service.submit(t1);
Future<Integer> submit2 = service.submit(t2);
Future<Integer> submit3= service.submit(t3);
//获取结果
Integer rs1 = submit1.get();
Integer rs2= submit2.get();
Integer rs3 = submit3.get();
//关闭服务
service.shutdown();
}
}
class TestCallable implements Callable<Integer>{
private Integer i;
@Override
public Integer call() throws Exception {
for (int i = 0; i < 200; i++) {
System.out.println("线程名称"+Thread.currentThread().getName()+"执行"+i+"步");
}
return i;
}
}
线程安全
1 重复代码:不同线程同时执行到了同一个语句(并行)
2 出现不存在的:在不同的线程中,执行过判断语句后,此线程的CPU执行权就被抢走,由其它线程开始执行,进过及格线程执行之后就会出现不存在的数(并发)
解决方法
1 同步代码块(不局限于一个对象)
锁对象一般是共享数据的代码,需要变化的量,即是需要增删改的 数据
2 同步方法(局限于只有一个对象)
synchronize 关键词,默认锁 是它的所在的类本身
同步方法的锁对象就是实现类对象
如果是静态的同步方法,锁对象就不是实现类对象,因为静态方法优先于对象
静态方法的锁对象是本类的class属性——class文件对象(反射)
3 锁
注意事项:在同步代码块和同步代码方法中,锁对象可以是任意对象,但是多线程之间必须是同一个对象
锁对象作用:把代码块锁住,使其只在一个线程类执行
线程通信
1 为什么要处理线程间通信:多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同执行完成一件任务,并且我们需要他们之间有规律的运行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据
2 如何保证线程之间通信有效利用:多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用和操作。就是多线程在操作同一份数据时,避免对同一共享变量的争夺。也就是说,需要一种手段来使各个线程能有效的利用资源,这种手段叫做——等待唤醒机制
1 wait()与notify()必须要由同一个锁对象调用,因为对应的锁对象可以通过notify()唤醒使用同一个锁对象调用的wait()后的线程
2 wait()和notify()都是属于object类的方法,因为锁对象可以是任意对象,而任意对象都是继承了object类
3 wait()和notify()必须在同步代码块或者同步代码方法中执行,因为,必须调用锁对象才可以调用这两个方法
4 每一个对象都有一把锁,sleep()方法不会释放锁
5 yield() 礼让方法,礼让线程,但是不一定成功
6 join(),插队方法,只有当插入的线程执行完成之后才可以继续执行下去,否则线程进入堵塞状态
9 wait()会释放锁,sleep()方法不会释放锁;wait() 需要在同步代码块中使用,sleep() 可以在任何地方使用
7 线程对象.setPriority() 和 getPriority(),是对于线程优先级的调度,但是线程优先级高的不一定先执行,优先级的高低只决定被CPU调用的概率,必须要先设置优先级再启动线程,不然无效
死锁
:多个线程互相持有对方需要的资源,形成僵持
避免方法:
1 不要形成锁嵌套
2 资源不共用
3 不要形成循环
4 可剥夺条件
线程池
背景:线程频繁的创建和销毁,特别消耗资源,若在并发编程下,性能将会受到很大的影响
优点:
1 提高了响应速度(减少了新线程的创建时间)
2 降低了资源消耗(重复利用线程池中的线程,减少了创建时间)
3 便于线程管理