多线程
进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。串行和并行。
串行:A->B->C
并行:查杀病毒、清理垃圾、电脑加速
线程的创建
1、继承Thread
- 继承 Thread 类
- 覆盖 run() 方法
- 直接调用 Thread#start() 执行
2、实现Runnable接口
- 实现Runnable接口
- 获取实现Runnable接口的实例,作为参数,创建Thread实例
- 执行 Thread#start() 启动线程
3、实现Callable接口,结合 FutureTask使用
- 实现Callable接口
- 以Callable的实现类为参数,创建FutureTask实例
- 将FutureTask作为Thread的参数,创建Thread实例
- 通过 Thread#start 启动线程
- 通过 FutreTask#get() 阻塞获取线程的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class AddCall implements Callable<Integer> {
private int start, end;
public AddCall(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Integer call() throws Exception {
int sum = 0;
System.out.println(Thread.currentThread().getName() + "start run");
for (int i = start; i < end; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName() + "end run");
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
int start = 0, mid = 500, end = 1000;
FutureTask<Integer> future1 = new FutureTask<Integer>(new AddCall(start, mid));
FutureTask<Integer> future2 = new FutureTask<Integer>(new AddCall(mid + 1,
运行结果:
线程1start run
线程1end run
线程2start run
线程2end run
ans:499000
4、利用该线程池ExecutorService、Callable、Future来实现
创建固定大小的线程池,提交Callable任务,利用Future获取返回的值
- 创建线程池(可以利用JDK的Executors,也可自己实现)
- 创建Callable 或 Runnable任务,提交到线程池
- 通过返回的 Future#get 获取返回的结果
import java.util.concurrent.*;
public class AddPool implements Callable<Integer> {
private int start, end;
public AddPool(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Integer call() throws Exception {
int sum = 0;
System.out.println(Thread.currentThread().getName() + "开始执行!");
for (int i = start; i <= end; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName() + "执行完毕!sum=" + sum);
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
int start = 0, mid = 500, end = 1000;
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<Integer> future1 = executorService.submit(new AddPool(start, mid));
Future<Integer> future2 = executorService.submit(new AddPool(mid + 1, end));
int sum =
执行结果:
pool-1-thread-2开始执行!
pool-1-thread-2执行完毕!sum=375250
pool-1-thread-1开始执行!
pool-1-thread-1执行完毕!sum=125250
sum+500500
线程安全
线程不安全:
A线程在进入方法后,拿到了count的值,刚把这个值读取出来,还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的
// An highlighted block
int num = 0;
public void getNum(){
count++;
System.out.println(num)
}
线程安全:
public void mothod(int j){
int i = 0;
j = j + i;
}
这段代码所执行的作用范围与执行结果只存在它这条线程的局部变量中,并且只能由正在执行的线程进行访问。
因为没有共享的数据,所以他们之间的行为,并不会影响其他线程的操作和结果,所以说无状态的对象,也是线程安全的。
如何确保线程安全
1、synchronized
Synchronized很简单,把需要线程安全的代码,放到Synchronized的花括号里面就行
2、用Lock(锁)
注意Lock lock一定要声明为成员变量,如果Lock lock是方法内的局部变量,每个线程执行该方法时都会保存一个副本,那么每个线程执行到lock.lock()处获取的是不同的锁,所以就不会对临界资源形成同步互斥访问
private Lock lock = new ReentrantLock();
private Runnable runnable = () -> {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 运行 " + count++);
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
};
3、用AtomicInteger(原子类)
用原子类的话,这里需要把变量声明为原子变量,自增时也要调用原子类的方法。用原子类是自带锁,保证线程安全同步,不用再附加其他锁。
public class Run {
private static AtomicInteger count = new AtomicInteger(1);
private Runnable runnable = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + " 运行 " + count.getAndIncrement());
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
public static void main(String[] args) throws InterruptedException {
Run r = new Run();
new Thread(r.runnable, "线程A").start();
new Thread(r.runnable, "线程B").start();
Thread.sleep(6000);
System.out.println("count值为:" + count);
}
}
Synchronized、Lock两种锁的区别
- Synchronized是关键字,内置语言实现,Lock是接口。
- Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
- Lock是可以中断锁,Synchronized是非中断锁,必须等待线程执行完成释放锁。
- Lock可以使用读锁提高多线程读效率。