一、创建执行线程的三种方法
①继承Thread类并且重写run()方法
②实现Runnable接口并且重写run()方法
③实现Callable接口并且重写run()方法
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。FutureTask 是 Future 接口的实现类
eg:
public class TestCallable {
public static void main(String[] args) {
CallableDemo callableDemo = new CallableDemo();
FutureTask<Integer> futureTask = new FutureTask<>(callableDemo);
new Thread(futureTask).start();
System.out.println("开始运行了");
try {
Integer integer = futureTask.get();//这里类似于闭锁
System.out.println("----------------");
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class CallableDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=1;i<=100000;i++){
sum++;
}
return sum;
}
}
二、wait(),notify(),notifyAll()用来操作线程为什么定义在Object类中
因为
-
这些方法必须存在于同步中;
-
使用这些方法必须标识同步所属的锁;
-
锁可以是任意对象,所以任意对象调用方法一定定义在Object类中。
-
只有同一个锁上的被等待线程,才可以被同一个锁上的线程唤醒(notify、notifyAll),不可以对不同锁中的线程进行唤醒。
三、Main线程结束,其他线程也结束吗?
进程是资源分配的基本单位,线程是CPU调度的基本单位。对于CPU来说,其实并不存在java的主线程和子线程之分,都只是个普通的线程。进程的资源是线程共享的,只要进程还在,线程就可以正常执行,换句话说线程是强依赖于进程的。也就是说,线程其实并不存在互相依赖的关系,一个线程的死亡从理论上来说,不会对其他线程有什么影响。
四、常见的锁
1、共享锁:又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
2、排他锁:又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
3、乐观锁:相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。每次去拿数据的时候都认为别人不会修改,所以不会上锁,在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS(用来保证数据变量的原子性)就是使用乐观锁的一个例子
4、悲观锁:正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。每次拿数据时,先上锁,其他的线程想要使用(读、改)该数据就需要先拿到锁。
乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。