进程(process)是操作系统的任务单元,每一个程序启动后操作系统都会为其分配进程编号(PID)
线程(Thread)是进程中的任务单元,程序启动的时候,首先会创建主线程,可以在主线程中开辟子线程,每一个线程都对应一个虚拟机栈,栈区是线程私有的,堆和方法区是线程共享的
串行
在一台机器上单线程执行
并行
并发:在同一台机器上多线程并行执行(存在资源竞争关系)
并行:在多台机器上并行执行(不存在资源竞争关系)
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Demo03 {
public static void main(String[] args) {
MyCallable mc = new MyCallable(); //创建一个Callable对象
FutureTask ft = new FutureTask<>(mc); //创建一个FutureTask对象,用Callable对象作为构造方法的参数
Thread t = new Thread(ft); //创建一个Thread对象,用FutureTask对象作为构造方法的参数
t.start();
//Integer sum = ft.get(); 会阻塞主线程
//用FutureTask对象的get()方法取子线程call方法的返回值
while (true){
try{
if(ft.isDone()){ //isDone()方法可以判定子线程有没有运行结束
Integer sum = ft.get(); //在取子线程返回值的时候,子线程可以正常返回也可以异常返回
System.out.println("子线程返回值:" + sum);
break;
}else {
System.out.println("子线程未结束");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
//静态内部类
//实现线程的方式3:实现Callable接口,重写call方法
//需要指定泛型,这个泛型是作为子线程返回值的数据类型
//实现线程的方式1和方式2都无法实现子线程运行后返回一个结果,因为run()方法的返回值类型是void
static class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
Integer sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
Thread.sleep(100);
}
return sum;
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo04 {
static final int CORE_POOL_SIZE = 5; //核心线程数
static final int MAX_POOL_SIZE = 10; //最大线程数
static final int QUEUE_CAPACITY = 100; //队列容量
static final long KEEP_ALIVE_TIME = 1L; //存活时间
static ThreadPoolExecutor executor = null; //静态指针
static {
//初始化线程池对象
executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
public static void main(String[] args) {
Runnable runnable = new myRunnable();
for (int i = 0; i < 100; i++) {
//executor是线程池指针,execute是线程池方法,参数是一个runnable
executor.execute(runnable);
}
}
static class myRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
方式1,2区别
当一个类已经有父类了,此时不方便继承Thread,可以采用实现Runnable接口的方式,Runnable共享资源也方便。
方式1,2和方式3的区别
方式1,2没有返回值,而且子线程出异常主线程捕捉不到,而方式3可以解决
锁是Java中用来保证线程操作原子性的一种机制
锁是数据库中用来保证事务操作原子性的一种机制
锁可以避免脏读
Java中的锁有:synchronized和lock锁
synchronized是关键字,可以锁代码块,也可以锁方法
lock是类(官方推荐),只能锁代码块
1.互斥条件:锁要具有排他性,在同一时刻锁只能被一个线程持有
2.请求与保持条件:一个线程因为请求其他资源被阻塞的时候,对已获取的资源保持不释放
3.不剥夺条件:一个线程没有主动释放资源之前,是不能被其他线程强行剥夺的
4.循环等待条件:A线程持有资源a的锁,B线程持有资源b的锁,在互相不释放自己持有的锁的情况下,去请求对方持有的锁,这时候会形成双方循环等待,造成永久阻塞
破坏任意一个条件即可
1.破坏互斥条件:用共享锁,在同一时刻,锁可以被多个线程持有
2.破坏请求与保持条件:一次性申请所有的资源
3.破坏不剥夺条件:一个线程因为请求其他资源被阻塞的时候,主动释放已获取的资源
4.破坏循环等待条件:所有线程按照同样的顺序请求资源即可
我们把数据类型分为线程安全类型和线程不安全类型
如果一个数据类型需要我们自己手动加锁来保证其操作的原子性,那么他就是线程不安全的数据类型。
如果一个数据类型能够自己在方法中加锁,来保证其操作的原子性,那么他就是线程安全的数据类型。
线程不安全 | 线程安全 |
---|---|
ArrayList | 1.Vector 2.CopyOnWriteArrayList |
HashMap | 1.Hashtable 2.ConcurrentHashMap |
String,StringBuilder | StringBuffer |
int,Integer | AtomicInteger |
其他。。。。。 |