并发/多线程: 工作基本不用,面试使劲问
进程和线程 Java没有多进程,也不能操纵进程。 Java只有多线程
程序是死的,安装/存放在硬盘上而已。 进程:程序的一次动态执行过程就是进程。
随着程序越来越大,进程也就会占用更多的资源,CPU在对其进行调度时也就会越发吃力。 比如30万人移动基地。
正是由于以上原因,我们发明了更加轻量级别的进程,即线程。 一个进程可以派生出多个线程。
这种拆分后,进程不再被进行调度,而是成为了资源持有的单位。 线程反而成为了资源调度的单位(只带有一点点运行所必须的资源)
线程是轻量级的进程,线程是调度的基本单元,进程是资源持有的基本单元。
写多线程
1. 写一个任务,任务是多线程到底要做什么事情。
任务应该写一个类实现Runnable接口,将任务的具体操作放入这个接口的run方法中。
public class PrintChar implements Runnable {
private char c;
public PrintChar(char c) {
this.c = c;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.printf("%-3c", c);
}
}
}
public class PrintNum implements Runnable {
private int num;
public PrintNum(int num) {
this.num = num;
}
@Override
public void run() {
for (int i = 1; i <= num; i++) {
System.out.printf("%-3d", i);
}
}
}
start: 开始执行
构造一个Thread对象,将任务放入这个Thread对象
调用Thread对象的start方法
public static void main(String[] args) {
PrintChar printChar = new PrintChar('A');
PrintNum printNum = new PrintNum(5);
// printChar.run();
// printNum.run();
Thread t1 = new Thread(printChar); // 构造了一个线程对象,这个线程的任务是打印A
Thread t2 = new Thread(printNum); // 构造了一个线程对象,但是这个线程并没有执行
t1.start(); // start才是让这个线程去执行对应的任务
// 这句话开始执行时,此时的程序
t2.start();
}
public class AccountWithoutSync {
private static Account account = new Account();
private static class Account{
private int balance = 0; // 余额
public int getBalance() { // 去除余额
return balance;
}
public synchronized void deposit(int amount) { // 存amount金额的钱
int newBalance = balance + amount;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = newBalance;
}
}
public static class AddPennyTask implements Runnable {
@Override
public void run() {
account.deposit(1);
}
}
public static void main(String[] args) {
// newCachedThreadPool根据需求开设线程
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++)
executorService.execute(new AddPennyTask());
executorService.shutdown();
while (!executorService.isTerminated());
// Thread.sleep(1000);
System.out.println(account.getBalance());
}
}
ExecutorService线程池
newCachedThreadPool:根据需求开设线程
x.join(); // 插入
Thread.sleep(100); // 睡眠
用Lambda表达式改写上面的代码
public static void main(String[] args) {
new Thread(() -> {
Thread x = new Thread(new PrintChar('X'));
x.start();
for (int i = 1; i <= 10; i++) {
if (i == 4){
try {
x.join(); // 插入
// Thread.sleep(100); // 睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Thread.yield(); // 让出CPU给其他线程优先执行,但是并不绝对
System.out.printf("%-3c", 'A');
}
}).start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
System.out.printf("%-3d", i);
}
}).start();
}
Thread类中的常见方法:
- sleep :睡眠
- yield :让出CPU给其他线程先执行
- join ://插入,这个线程无论如何在被加塞的线程前优先做完。
sleep()和对象.join的结果一定是可以确定的
yield()的结果是完全不确定的
public static void main(String[] args) {
// ExecutorService 或者其父类接口 Executor 就被认为是线程池
// Executors 是一个用于创建线程池的工具类
// newFixedThreadPool 用于创建一个具有固定数量的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 一个三线程的线程池只是说可以同时开三个线程,多余的任务可以等等
// execute方法类似于start方法,用于执行一个线程
executor.execute(new PrintChar('A'));
executor.execute(new PrintChar('B'));
executor.execute(new PrintChar('C'));
executor.execute(new PrintChar('D'));
// shutdown用于关闭线程池,这个关闭虽然不是阻塞的,但是它会等待线程池中所有线程全部执行完毕后才关闭线程池
executor.shutdown();
}
线程同步: 让多个线程按照我们的要求有序执行
public class AccountWithoutSync {
private static Account account = new Account();
private static class AddPennyTask implements Runnable {
@Override
public void run() {
account.deposit(1);
}
}
private static class Account{
private int balance = 0; //余额
public int getBalance() { //取出余额
return balance;
}
public void deposit(int amount) { //存amount金额的钱
int newBalance = balance + amount;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = newBalance;
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
//newCachedThreadPool根据需求开设线程
for (int i = 0; i < 100; i++)
executorService.execute(new AddPennyTask());
executorService.shutdown();
while (!executorService.isTerminated());
//isTerminated用于判断线程池是否已经全部做完
System.out.println(account.getBalance());
}
}
public class Account {
private final Object o = new Object();
private int balance = 0; //余额, 多个线程进行争抢的资源被称作“临界资源”
public int getBalance() { //取出余额
return balance;
}
public void deposit(int amount) { //存amount金额的钱
//用于操纵临界资源的代码段被称作“临界区”
synchronized (o) { //同步块
int newBalance = balance + amount;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = newBalance;
}
}
}
public class AddPennyTask implements Runnable{
private final Account account;
public AddPennyTask(Account account) {
this.account = account;
}
@Override
public void run() {
account.deposit(1);
}
}
public class AccountWithoutSync {
public static void main(String[] args) throws InterruptedException {
Account account = new Account();
ExecutorService executorService = Executors.newCachedThreadPool();
//newCachedThreadPool根据需求开设线程
for (int i = 0; i < 100; i++)
executorService.execute(new AddPennyTask(account));
executorService.shutdown();
while (!executorService.isTerminated());
//isTerminated用于判断线程池是否已经全部做完
System.out.println(account.getBalance());
}
}
线程间协作:两个线程根据具体情况有条不紊的相互合作。
public class Account {
private int balance = 0; //余额, 多个线程进行争抢的资源被称作“临界资源”
private Lock lock = new ReentrantLock(); //获得一把锁
private Condition condition = lock.newCondition(); //锁的条件,也叫条件锁
public int getBalance() { //取出余额
return balance;
}
public void withdraw(int amount) {
lock.lock();
try{
while(balance < amount) {
System.out.printf("\t\t\t现在我打算取%d元,但是余额不足,暂时等待\n", amount);
condition.await(); //await将当前这个线程自我阻塞
}
balance -= amount;
System.out.printf("\t\t\t已经取了%d元钱,账户余额是%d.\n", amount, getBalance());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void deposit(int amount) {
lock.lock();
try {
balance += amount;
System.out.println("已经存入" + amount);
condition.signalAll(); //signal用于随机释放一个正在阻塞的进程,加all用于释放所有被阻塞的进程
} finally {
lock.unlock();
}
}
}
public class DepositTask implements Runnable{
private final Account account;
public DepositTask(Account account) {
this.account = account;
}
@Override
public void run() {
while(true) {
account.deposit((int)(Math.random() * 10));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class WithdrawTask implements Runnable{
private final Account account;
public WithdrawTask(Account account) {
this.account = account;
}
@Override
public void run() {
while(true)
account.withdraw((int)(Math.random() * 10));
}
}
public class AccountWithoutSync {
public static void main(String[] args) throws InterruptedException {
Account account = new Account();
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new DepositTask(account));
executorService.execute(new WithdrawTask(account));
executorService.shutdown();
}
}
使用synchronized改写上面的代码
public class Account {
private int balance = 0; //余额, 多个线程进行争抢的资源被称作“临界资源”
public int getBalance() { //取出余额
return balance;
}
public synchronized void withdraw(int amount) {
try {
while (balance < amount) {
System.out.printf("\t\t\t现在我打算取%d元,但是余额不足,暂时等待\n", amount);
wait();
}
balance -= amount;
System.out.printf("\t\t\t已经取了%d元钱,账户余额是%d.\n", amount, getBalance());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void deposit(int amount) {
balance += amount;
System.out.println("已经存入" + amount);
notifyAll();
}
}
改成同步块之后的版本:
public class Account {
private int balance = 0; //余额, 多个线程进行争抢的资源被称作“临界资源”
public int getBalance() { //取出余额
return balance;
}
public void withdraw(int amount) {
synchronized (this) {
try {
while (balance < amount) {
System.out.printf("\t\t\t现在我打算取%d元,但是余额不足,暂时等待\n", amount);
wait();
}
balance -= amount;
System.out.printf("\t\t\t已经取了%d元钱,账户余额是%d.\n", amount, getBalance());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void deposit(int amount) {
synchronized (this) {
balance += amount;
System.out.println("已经存入" + amount);
notifyAll();
}
}
}
生产者和消费者
public class Buffer {
private static final int CAPACITY = 1;
private final Queue<Integer> queue = new ArrayDeque<>(CAPACITY);
private final Lock lock = new ReentrantLock();
private final Condition write = lock.newCondition();
private final Condition read = lock.newCondition();
public void write(int value) {
lock.lock();
try{
while(queue.size() == CAPACITY) {
System.out.println("生产者现在暂停");
write.await();
}
//程序如果能执行到此处,就意味着仓库中是不满的,可以生产
queue.offer(value);
read.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public int read() {
int value = 0;
lock.lock();
try{
while (queue.isEmpty()) {
System.out.println("\t\t\t仓库中为空,暂停消费");
read.await();
}
//程序运行到此处,意味着仓库中非空,可以消费,同时也可以生
value = queue.poll();
write.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return value;
}
}
public class ProducerTask implements Runnable { private Buffer buffer;
public ProducerTask(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
int i = 1;
while(true) {
buffer.write(i);
System.out.println("生产了对象:" + i++);
try {
Thread.sleep((int)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ConsumerTask implements Runnable{ private Buffer buffer;
public ConsumerTask(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true) {
System.out.println("\t\t\t消费者消费了: " + buffer.read());
try {
Thread.sleep((int)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class Test { public static void main(String[] args) { Buffer buffer = new Buffer(); ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(new ProducerTask(buffer)); executorService.execute(new ConsumerTask(buffer)); executorService.shutdown(); } }
阻塞队列 删除Buffer类,变成阻塞队列。 package com.example;
import java.util.concurrent.ArrayBlockingQueue;
public class ProducerTask implements Runnable { private ArrayBlockingQueue<Integer> buffer;
public ProducerTask(ArrayBlockingQueue<Integer> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
int i = 1;
while(true) {
try {
buffer.put(i);
System.out.println("生产者生产了对象:" + i++);
Thread.sleep((int)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
public class ConsumerTask implements Runnable{ private ArrayBlockingQueue<Integer> buffer;
public ConsumerTask(ArrayBlockingQueue<Integer> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true) {
try {
System.out.println("\t\t\t消费者消费了: " + buffer.take());
Thread.sleep((int)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
ArrayBlockingQueue<Integer> buffer = new ArrayBlockingQueue<>(1);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new ProducerTask(buffer));
executorService.execute(new ConsumerTask(buffer));
executorService.shutdown(); }
}
阻塞队列
删除Buffer类,变成阻塞队列
public class ProducerTask implements Runnable { private ArrayBlockingQueue<Integer> buffer;
public ProducerTask(ArrayBlockingQueue<Integer> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
int i = 1;
while(true) {
try {
buffer.put(i);
System.out.println("生产者生产了对象:" + i++);
Thread.sleep((int)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ConsumerTask implements Runnable{ private ArrayBlockingQueue<Integer> buffer;
public ConsumerTask(ArrayBlockingQueue<Integer> buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(true) {
try {
System.out.println("\t\t\t消费者消费了: " + buffer.take());
Thread.sleep((int)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
ArrayBlockingQueue<Integer> buffer = new ArrayBlockingQueue<>(1);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new ProducerTask(buffer));
executorService.execute(new ConsumerTask(buffer));
executorService.shutdown();
}
}
信号量
信号量就好比是一把锁有1个或多个钥匙。 比如一个被信号量包起来的代码(类似于同步块)可以限制有几个线程能够同时访问。
死锁:多个线程为了争抢有限的资源,从而导致谁都没办法获得全部资源得以运行。 哲学家就餐
线程的几种状态及其之间的转换
- 新建(新生)——一个线程被新建,就是new一个Thread
- 就绪——一个线程已经拥有了运行所需要的全部资源,唯独只差CPU yield()
- 运行——同一时间只能有一个线程是运行的。 run()
- 阻塞(等待)——线程因为缺乏某种资源导致无法就绪 4.1 等待目标完成——某个线程在等待某个事件结束,join加塞的必须先做完 4.2 等待超时——某个线程因为时间问题导致无法运行,sleep睡眠 4.3 等待通知——等待其余线程通知其继续运行,wait自我阻塞
- 结束(死亡)——线程运行结束了
操作系统中规定,只有就绪态才能转为运行态 在操作系统中,会有很多线程处于就绪状态,这些线程通常都被放在一个就绪队列中
public class AddTask implements Runnable{ private List<Integer> list; AtomicInteger num;
public AddTask(List<Integer> list, AtomicInteger num) {
this.list = list;
this.num = num;
}
@Override
public void run() {
System.out.println(num.incrementAndGet());
for (int i = 0; i < list.size(); i++) {
list.set(i, list.get(i) + 1); //+1操作不是多线程安全的,此处只有get和set方法才是多线程安全的
}
}
public class Test { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1);list.add(1);list.add(1); AtomicInteger num = new AtomicInteger(0);
List<Integer> syncList = Collections.synchronizedList(list);
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
executorService.execute(new AddTask(syncList, num));
}
executorService.shutdown();
while(!executorService.isShutdown());
System.out.println(num);
}