线程
1.1.线程的概念
进程和线程的区别:
进程是资源(CPU,内存)分配的基本单位,是静态的概念
线程是程序运行的最小单位,真正干活的
进程中至少会有一个线程
并行和并发:
并发:是指同时发生了,程序支持并发而已,任务同时发生了;
并行:是同时进行,同时执行某些任务
在单核CPU情况下,并不是真正的多线程,只不过是CPU在多个线程间进行快速切换,用户就认为线程在同时运行而已;
1.2.创建线程
1.继承Thread类,重写run方法
public class MyThread extends Thread {
@Override
public void run() {
// 线程的执行逻辑
for (int i = 0; i < 20; i++) {
System.out.println("线程: " + i);
}
}
}
测试
public static void main(String[] args) {
System.out.println("main 线程执行开始");
// 创建线程对象
Thread threadA = new MyThreadA();
Thread threadB = new MyThreadB();
//start方法 只是告诉OS调度器 线程准备好了而已 时间片
threadA.start();
threadB.start();
System.out.println("main 线程执行完了");
}
2.实现Runnable接口
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
for (int l = 0; l < 20; l++) {
System.out.println("线程A " + l);
}
}
}, "ThreadA");
Thread threadB = new Thread(() -> {
for (int l = 0; l < 20; l++) {
System.out.println("线程B " + l);
}
}, "ThreadB");
threadA.start();
threadB.start();
3.线程执行完之后如果需要得到线程的返回结果,需要使用到线程间的通信;
实现 Callable接口,
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
for (int i = 0; i < 2000; i++) {
System.out.println(i);
}
return 120;
}
});
Thread thread = new Thread(futureTask);
thread.start();
try {
// get()方法是阻塞时的方法,必须等线程执行完了才会得到返回值
Object o = futureTask.get();
System.out.println("线程的返回值是:" + o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("主线程执行完了");
4.线程常用方法
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
for (int l = 0; l < 10; l++) {
System.out.println("线程A " + l);
}
}
}, "ThreadA");
Thread threadB = new Thread(() -> {
for (int l = 0; l < 10; l++) {
System.out.println("线程B " + l);
}
}, "ThreadB");
threadB.start();
System.out.println("线程A的状态:" + threadA.getState());
threadA.start();
System.out.println("线程A的状态:" + threadA.getState());
try {
//当前线程休眠1秒 单位是毫秒
// Thread.sleep(1000L);
// TimeUnit.SECONDS.sleep(1L);
//返回一个当前线程
Thread thread = Thread.currentThread();
// 获取线程名
String name = thread.getName();
// 获取线程ID
long id = thread.getId();
System.out.println("线程ID是:" + id);
// 当前线程执行结束了,其他线程才能继续执行
threadA.join();
// 让出CPU时间片,给其他线程进行调度
// Thread.yield();
System.out.println("线程A的状态:" + threadA.getState());
// 优先级
int priority = threadA.getPriority();
//
System.out.println("线程A的优先级是:" + priority);
} catch (Exception e) {
}
1.3.线程池
频繁创建线程和销毁线程会消耗系统资源;使用线程池可以节省系统资源
// newFixedThreadPool生成一个固定数量线程的线程池
// ExecutorService pool = Executors.newFixedThreadPool(100);
// 10 200 创建一个数量可变的线程池
ExecutorService pool2 = Executors.newCachedThreadPool();
pool2.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
System.out.println(Thread.currentThread().getId());
}
});
pool2.submit(() -> {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
return 1;
});
//把线程池关闭,
pool2.shutdown();
// pool.shutdown();
1.4. 线程安全问题
线程安全问题:多线程情况下,同时操作主存中某个数据的时候,出现数据不一致情况
1.4.1.线程同步
同步:一个任务的执行需要等待另一个任务的结束
异步:任务可以同时进行
1.synchronized关键字,内部JVM进行底层实现
-
每个对象都有一把对象锁,同一时刻只能有一个线程获取这个对象的对象锁
-
synchronized可以用在方法声明处,也可以用在方法体中
-
是可重入锁,不需要重写获取锁 ;排他锁/互斥锁(线程A获取对象锁之后如果未释放,线程B是不能获取到的,所以叫互斥锁)
-
如果在方法声明处方法执行完就释放对象锁,如果是方法体,方法体执行完就释放锁;如果在执行过程中出现异常也会释放锁;当线程调用wait方法的时候 也会释放锁;调用sleep方法不会释放对象锁;
银行账户
public class BankCount {
private double balance;
public double getBalance() {
return balance;
}
/**
* 存钱
* @param money
*/
public synchronized void saveMoney(double money) {
// 1,取出balance的值
// 2,add
// 3, 赋值之后刷新到主存 3条指令要在一个原子性的操作中,不能被打断
balance = balance + money;
}
/**
* 取钱
* @param money
*/
public void drawMoney(double money) {
// () 表示要获取谁的对象锁,synchronized代码块的范围比synchronized方法小
synchronized (this) {
balance -= money;
}
}
}
测试类
public static void main(String[] args) {
// 引用/对象/实例
BankCount bankCount = new BankCount();
Thread threadA = new Thread(() -> {
int m = 0;
for (int i = 0; i < 200000; i++) {
bankCount.saveMoney(10);
m++;
}
System.out.println("threadA end");
});
Thread threadB = new Thread(() -> {
int m = 0;
for (int i = 0; i < 200000; i++) {
bankCount.drawMoney(10);
m++;
}
System.out.println("threadB end");
});
threadA.start();
threadB.start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前余额:" + bankCount.getBalance());
}
在静态方法中的使用 synchronized
/**
* synchronized在静态方法中使用需要的是类对象(class)的对象锁
*/
public synchronized static void method() {
System.out.println("=======");
}
public static void method2() {
synchronized (BankCount.class) {
}
}
2.Lock的使用,lock后的代码写入try语句块中,释放锁写入finally中
public class BankCount2 {
private double balance;
private ReentrantLock lock = new ReentrantLock();
/**
* 存钱
* @param money
*/
public void saveMoney(double money) {
// 上锁
lock.lock();
try {
balance = balance + money;
} catch (Exception e) {
} finally {
// 释放锁,一定要执行
lock.unlock();
}
}
/**
* 取钱
* @param money
*/
public void drawMoney(double money) {
// () 表示要获取谁的对象锁,synchronized代码块的范围比方法小
lock.lock();
try {
balance -= money;
} finally {
lock.unlock();
}
}
public double getBalance() {
return balance;
}
}
1.4.1.线程安全的类
1.集合ArrayList和Vector
public static void main(String[] args) throws InterruptedException {
//ArrayList是线程不安全的,Vector是线程安全的
// ArrayList<Integer> integers = new ArrayList<>();
Vector<Integer> integers = new Vector<>();
new Thread(() -> {
for (int i = 0; i < 100000; i++) {
integers.add(i);
}
}).start();
new Thread(() -> {
for (int i = 0; i < 100000; i++) {
integers.add(i);
}
}).start();
TimeUnit.SECONDS.sleep(1L);
System.out.println(integers.size());
}
2.AtomicInteger类
public class DeadLock {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
task2();
}
public static void task2() {
Thread threadA = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
count.incrementAndGet();
}
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
count.decrementAndGet();
}
});
threadA.start();
threadB.start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count的值是:" + count);
}
}
1.5.死锁
多个线程间出现互相等待对方释放锁的情况,就是死锁
解决:嵌套上锁的时候,上锁的顺序保持一致
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
new Thread(() -> {
synchronized (o1) {
System.out.println("线程A获取到1号锁了");
try {
TimeUnit.SECONDS.sleep(1L);
synchronized (o2) {
System.out.println("线程A获取到2 号锁了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (o2) {
System.out.println("线程B获取到2号锁了");
try {
TimeUnit.SECONDS.sleep(1L);
synchronized (o1) {
System.out.println("线程B获取到1 号锁了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) {
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
new Thread(() -> {
// 和lock 的区别,不会无限等待,获取不到直接返回false
lock1.tryLock();
System.out.println("线程A获取到1号锁了");
try {
TimeUnit.SECONDS.sleep(1L);
lock2.tryLock();
System.out.println("线程A获取到2 号锁了");
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.unlock();
lock1.unlock();
}).start();
new Thread(() -> {
lock2.tryLock();
System.out.println("线程B获取到2号锁了");
try {
TimeUnit.SECONDS.sleep(1L);
if (lock1.tryLock()) {
System.out.println("线程B获取到1 号锁了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.unlock();
lock1.unlock();
}).start();
}
1.6.生产者消费者
1.wait()方法和notify()方法
- 只能在同步方法或者同步代码块中使用,而且应该在循环中使用
- this.notify()表示随机唤醒一个当前对象等待池中的线程,this.notifyAll()是唤醒当前对象等待池中的所有线程
仓库类
public class Store {
// 记录商品数量
private int count;
private final int MAX_COUNT = 100;
/**
* 生成商品,
*/
public synchronized void product() {
while (count >= MAX_COUNT) {
System.out.println("仓库已满,生产者需要等待");
try {
// wait()是让当前线程进入当前对象的等待池,而且会释放对象锁
// 当线程被唤醒之后,应该继续判断条件,如果还是不满足需要继续等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println("产出一件商品,当前数量是 " + count);
// 只能在同步方法或者同步代码块中调用
this.notifyAll();
}
/**
* 消费商品
*/
public synchronized void pop() {
while (count <= 0) {
System.out.println("没有商品了,消费者需要等待了");
try {
// wait()是让当前线程进入当前对象的等待池,而且会释放对象锁
// 当线程被唤醒之后,应该继续判断条件,如果还是不满足需要继续等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println("消费一件商品,当前数量是 " + count);
// 唤醒生产者 this.notify()表示随机唤醒一个当前对象等待池中的线程,this.notifyAll()是唤醒当前对象等待池中的所有线程
this.notifyAll();
}
}
测试类
Store store = new Store();
Thread product = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
store.product();
}
});
Thread pop = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
store.pop();
}
});
product.start();
pop.start();
面试题
1,sleep和wait方法的区别
- sleep 是线程的静态方法,wait是Object的方法
- sleep可以在任意地方使用,wait只能在同步方法或者同步代码块中使用
- sleep是不释放对象锁,wait方法会释放对象锁
1.7.多线程的三个核心概念
- 原子性
一个操作要么全部执行,要么全部不执行;银行转账案例,都要符合原子性
synchronized可以满足原子性
2.可见性
多个线程访问共享变量的时候,一个线程对共享变量的操作,其他线程可见
synchronized可以保证可见性
volatile可以保证可见性
3.顺序性
JVM底层有指令重排序,代码执行顺序不一定就是你认为的顺序;单线程情况下绝对没有问题;
执行过程根据程序代码的顺序来进行;
volatile可以保证顺序性,防止JVM进行指令重排序
1.8. volatile关键字
- volatile只能修饰成员变量
1.8.1. volatile保证可见性
public class Demo {
//volatile可以保证可见性
private volatile static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
}
}).start();
TimeUnit.SECONDS.sleep(1L);
flag = false;
System.out.println("主线程结束了");
}
}
1.8.2. volatile保证顺序性
设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式
一个类只能有一个实例,管理器类/调度器类
实现单例的三个关键点:
1,构造方法私有化
2,提供一个本类型的静态成员变量
3,提供一个静态的共有方法,返回值类型是本类型
1.8.2.1. 饿汉式单例
- 实例提前创建好,线程安全的
- 如果后期没有用到此单例,内存空间浪费
使用场景:如果此单例占用内存比较小,可以使用饿汉式单例进行创建
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
1.8.2.2. 懒汉式单例
1.初级版本
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
2.多线程情况下,代码继续改进,保证多线程情况下只能有一个实例
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
3.性能较低,会出现线程间的等待;继续升级
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
// 双重检测,double check
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
4.最终版
public class LazySingleton {
// volatile可以防止指令重排序
private volatile static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
// 双重检测,double check
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
//JVM 指令重排序; 在单线程不会有数据不一致情况
// 1, new 实例 10S
// 2,给引用赋值 2S
// 3,return 引用 1S 13S
// 231 3S 因为指令重排序的问题,在实际使用过程中会出现对象状态不正确的现象
instance = new LazySingleton();
}
}
}
return instance;
}
}
1.8.2.3.单例的攻破
1.使用反射攻破
Class<?> aClass = Class.forName("com.qy28.sm.design.LazySingleton");
// 得到无参构造
Constructor<?> constructor = aClass.getDeclaredConstructor();
//把访问权限设置为可访问
constructor.setAccessible(true);
Object o1 = constructor.newInstance();
Object o2 = constructor.newInstance();
Object o3 = constructor.newInstance();
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);
- 使用序列化攻破
LazySingleton instance = LazySingleton.getInstance();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("obj.txt"));
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("obj.txt"))) {
objectOutputStream.writeObject(instance);
Object object = objectInputStream.readObject();
System.out.println(object);
System.out.println(instance);
System.out.println(object == instance);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
1.8.2.4.使用枚举类实现单例
只有一个实例,使用反射或者序列化都不能有其他实例
public enum EnumSingleton {
INSTANCE;
}
1.9.多线程常见面试题
1.sleep和wait方法的区别
- sleep 是线程的静态方法,wait是Object的方法
- sleep可以在任意地方使用,wait只能在同步方法或者同步代码块中使用
- sleep是不释放对象锁,wait方法会释放对象锁
2.synchronized和volatile的区别
- volatile 是变量修饰符;synchronized 是修饰 方法、代码段。
- volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
- volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
- volatile可以保证顺序性
3.synchronized 和 Lock 有什么区别?
- synchronized 可以给方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
4.什么是死锁以及如何防止
- 两个线程互相持对方释放所需要的锁,而发生的阻塞现象,我们称为死锁。
- 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
- 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
- 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
- 尽量减少同步的代码块。
5.runnable 和 callable 有什么区别?
- runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。
6.notify()和 notifyAll()有什么区别?
- notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
7.atomic的原理
- atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升