进程和线程的区别:
进程:它是系统分配资源的最小单位。
线程:它时系统调度的最小单位。
1.主线程:主要执行业务的线程
2.子线程:在主线程中创建线程就叫子线程
1.进程是系统分配资源的最小单位,线程是系统调度的最小单位。
2.一个进程中可以包含多个线程。
3.进程的执行单位就是线程。
4.进程间不可以像线程间资源共享。
5.一个进程至少包含一个线程,且至少有一个主线程。
线程的三种休眠方式:
import java.util.concurrent.TimeUnit;
public class ThreadDemo1 {
public static void main(String[] args) throws InterruptedException {
String content = "留声机在光影里飘来悠悠的乐声," +
"夏日的午后,靠在藤椅上,让摇椅随乐声晃动,绿色的虎皮蕉在脚边肆意蔓延微醺着,半梦半醒。";
for (char item : content.toCharArray()) {
System.out.print(item);
// // 方式1:线程休眠
// Thread.sleep(60 * 1000);
//
// // 方式2:线程休眠
// TimeUnit.SECONDS.sleep(1); // 休眠 1 秒
// TimeUnit.HOURS.sleep(1); // 休眠 1 小时
// 方式3:线程休眠
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
}
}
}
线程的6种创建:
public class ThreadDemo3 {
//线程创建方式一
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println(Thread.currentThread().getName());
}
}
public class ThreadDemo4 {
public static void main(String[] args) {
//线程创建方式二
Thread thread = new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
thread.start();
}
}
public class ThreadDemo5 {
//线程创建方式三
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
public class ThreadDemo6 {
//线程创建方式四
public static void main(String[] args) {
//创建匿名Runnable类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
thread.start();
}
}
public class ThreadDemo7 {
public static void main(String[] args) {、
//线程创建方式五
//lambda + runnable 只有jdk1.8之后才能支持
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
thread.start();
}
}
public class ThreadDemo8 {
//线程创建方式六
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
int num = task.get();
System.out.println(num);
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(10);
System.out.println("线程随机数:"+num);
return num;
}
}
}
线程是不是创建的越多越好?
不是 对于计算密集型任务来说:线程数=cpu核数最好 对于读写文件操作来说:理论上线程越多越好
线程分类:
1.用户线程:(默认创建的线程就是用户线程)。
2.守护线程:为用户线程服务。(守护线程的经典使用场景:垃圾回收器)。
守护线程需要注意的事项?
1.守护线程的设置必须在开始(start())之前。
2.在守护线程中默认创建的线程就是守护线程
Thread thread = new Thread(() -> {
// 新创建了一个线程
Thread t1 = new Thread(() -> {
}, "t1");
System.out.println("t1 守护线程:" + t1.isDaemon());
System.out.println("线程名:" +
Thread.currentThread().getName());
});
System.out.println("thread 守护线程:" + thread.isDaemon());
// 设置守护线程
thread.setDaemon(true);
thread.start();
System.out.println("thread 守护线程:" + thread.isDaemon());
run()vs start()?
1.run方法是一个对象的普通方法,它的使的是主线程来执行任务的。
2.start是线程开始的方法,它使用新的线程来执行任务
3.run方法可以被调用多次,而start方法只能被执行一次
线程的二种终止方式:
1.自定义全局表示来中断线程:
public class ThreadDemo17 {
// 全局变量
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (!flag) {
System.out.println("我正在转账...");
// try {
// // 休眠线程
// Thread.sleep(100);
// System.out.println("我正在转账...");
// } catch (InterruptedException e) {
// e.printStackTrace();
// break;
// }
}
System.out.println("啊?差点误了大事。");
}, "张三");
// 开启任务
t1.start();
// 休眠主线程一段时间
Thread.sleep(310);
// 终止线程
System.out.println("停止交易,有内鬼.");
flag = true;
}
}
2.使用Thread的interrupt()去终止:
public class ThreadDemo18 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (!Thread.interrupted()) {
try {
// 休眠线程
Thread.sleep(100);
System.out.println("我正在转账...");
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("啊?差点误了大事。");
}, "张三");
// 开启任务
t1.start();
// 休眠主线程一段时间
Thread.sleep(310);
// 终止线程
System.out.println("停止交易,有内鬼.");
t1.interrupt();
}
}
使用系统的 Intrruput()可以及时的终止线程,而使用自定义全局变量终止线程的方式,不能里面终止。
判断线程是否终止的方式:
1.Thread.interrupted():第一次接收到的终止状态方式是true,之后状态会复位为false
public class ThreadDemo20 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
// System.out.println(Thread.currentThread().isInterrupted());
}
});
thread.start();
// 终止线程
thread.interrupt();
}
}
2.Thread.currentThread ( ).isInterrupted ():只用来得到线程的状态,不会进行复位。
public class ThreadDemo20 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
// System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
}
});
thread.start();
// 终止线程
thread.interrupt();
}
}
等待一个线程:
public class ThreadDemo21 {
public static void main(String[] args) throws InterruptedException {
// 定义统一的任务
Runnable runnable = new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "上班");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + "下班");
}
};
Thread t1 = new Thread(runnable, "张三");
t1.start();
// 等待线程 t1 执行完成
t1.join(1200);
Thread t2 = new Thread(runnable, "李四");
t2.start();
}
}
线程的状态:
public class ThreadDemo23 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行之前:" + t1.getState());
// 开启线程
t1.start();
System.out.println("start 之后:" + t1.getState());
Thread.sleep(100);
System.out.println("休眠了100毫秒之后的状态:" + t1.getState());
// 等待执行完成
t1.join();
System.out.println("线程最终状态:" + t1.getState());
}
}
线程 yield()方法:
用来让出cpu的执行权的。yield分配执行权不一定成功,要看cpu的最终选择,但总体来说还是基本符合预期。
public class ThreadDemo24 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
// 出让 cpu 的执行权
Thread.yield();
System.out.println("我是线程1");
}
});
t1.start();
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("我是线程2");
}
});
t2.start();
}
}
多线程带来的问题:
非线程安全:使用多线程执行任务,最终得到的结果和预期不一致。
class Counter {
// 私有变量 count
private int count = 0;
// 执行循环次数
private final int maxSize = 10000000;
// 执行加法
public void incrment() {
for (int i = 0; i < maxSize; i++) {
count++;
}
}
// 执行减法
public void decrment() {
for (int i = 0; i < maxSize; i++) {
count--;
}
}
// 提供变量 count 的查询
public int getCount() {
return count;
}
}
public class ThreadDemo26 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
counter.incrment();
});
t1.start();
Thread t2 = new Thread(() -> {
counter.decrment();
});
t2.start();
// 等待执行完成
t1.join();
t2.join();
System.out.println("最终结果:" + counter.getCount());
}
}
线程不安全的因素:
1.cpu是抢占式执行的(万恶之源)
2.通过操作的同一个变量(具体见上边图)
3.可见性
4.非原子性
(我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入
房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性
的)
5.编译器优化(在多线程时,就会发生线路紊乱,也叫做指令重排)
线程不安全解决方案分析:
1.cpu抢占式执行(不可控)
2.每个线程操作自己的私有变量(有可能可以,但代价太大)
3.只要在关键步骤上排队执行就行了(加锁)
Volatile关键字(轻量级解决安全问题)
作用:解决内存不可见和指令重排的问题。但不能解决原子性问题
操作锁的流程:
尝试获取锁----->使用锁(具体业务)------>释放锁
java中加锁操作有两种
1.synchronized(jvm层的解决方案)
2.手动 lock 的方式
synchronized
synchronized的底层是使用操作系统的mutex lock(互斥锁)实现的。
在JVM层面是监视器锁
在java层面是将锁的信息存储在对象的头信息的偏向锁ID字段中
当线程释放锁时,JMM(java内存模型)会把该线程对应的工作内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码 必须从主内存中读取共享变量
synchronized用的锁是存在Java对象头里的
synchronizednei实现了获取锁以及释放锁的过程
synchronized如何实现线程安全?
synchronized的3种使用场景:
1.使用synchronized修饰代码块(可以给任意对象加锁)
public class ThreadDemo31 {
// 全局变量
private static int number = 0;
// 循环的最大次数
private static final int maxSize = 100000;
public static void main(String[] args) throws InterruptedException {
// 声明锁对象
Object lock = new Object();
Object lock2 = new Object();
// ++
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < maxSize; i++) {
// 实现加锁
synchronized (ThreadDemo31.class) {
// 代码1
number++;
}
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < maxSize; i++) {
synchronized (ThreadDemo31.class) {
number--;
}
}
}
});
t2.start();
// 等待两个线程执行完成
t1.join();
t2.join();
System.out.println("最终执行结果:" + number);
}
}
2.使用synchronized修饰静态方法(对当前的类进行加锁)
public class ThreadDemo32 {
// 全局变量
private static int number = 0;
// 循环次数
private static final int maxSize = 100000;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
increment();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
decrement();
}
});
t2.start();
t1.join();
t2.join();
System.out.println("最终执行结果:" + number);
}
// 相加
public synchronized static void increment() {
for (int i = 0; i < maxSize; i++) {
number++;
}
}
// 相减
public synchronized static void decrement() {
for (int i = 0; i < maxSize; i++) {
number--;
}
}
}
3.使用synchronized来修饰普通方法(对当前的类实例进行加锁)
public class ThreadDemo33 {
// 全局变量
private static int number = 0;
// 循环次数
private static final int maxSize = 100000;
public static void main(String[] args) throws InterruptedException {
ThreadDemo33 threadDemo33 = new ThreadDemo33();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
threadDemo33.increment();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
threadDemo33.decrement();
}
});
t2.start();
t1.join();
t2.join();
System.out.println("最终执行结果:" + number);
}
// 相加
public synchronized void increment() {
for (int i = 0; i < maxSize; i++) {
number++;
}
}
// 相减
public synchronized void decrement() {
for (int i = 0; i < maxSize; i++) {
number--;
}
}
}
注意:同一业务的多线程执行,一定要使用同一把锁;
锁升级的过程
重量级的锁(1.6之前):用户态 ->内核态(有特别大的性能消耗)
synchronized 锁升级的过程(jdk 1.6)
lock手动锁:
模板:
注意事项:释放锁一定要放在finally里面,lock()一定要放在try()外边。
如果放在 try里面可能会造成两个问题:
1.如果try里而抛出异常了,还没有加锁成功就执行finally里面的释放锁的操作了。因为还没有得到锁就释放锁。
2.如果放在try里面,如果没有锁的情况下试图释放锁,这个时候产生的异常就会将业务代码(也就是try里面的异常)给吞噬掉(覆盖掉),增加了代码调试的难度。
公平锁和非公平锁
非公平锁:性能更高。
1.当一个线程释放锁之后
2.另一个线程刚好执行到获取锁的代码就可以直接获取锁
公平锁:
1,一个线程释放锁
2。唤醒“需要得到锁” 的队列来得到锁
在Java语言中所有锁的默认实现方式都是非公平锁
synchronized是非公平锁
ReentrantLock默认是非公平锁,但可以现实的声明为公平锁
synchronized和lock的区别
区别方面 | synchronized | lock |
---|---|---|
加锁 /解锁 | 自动 | 手动 |
层面实现 | jvm层面锁的实现 | java层面锁的实现 |
安全锁/非安全锁 | 非公平锁 | 默认是非公平锁,但可以现实的声明为公平锁 |
适用范围 | 修饰代码块,修饰静态、普通方法 | 修饰代码块 |
灵活性 | 低 | 高 |
死锁
定义:在两个或两个以上线程的线程运行中,因为资源抢占而造成线程一直等待的问题。
造成死锁的四个条件:
1.互斥条件:当资源被一个线程拥有,就不能 被其他线程拥有了。
2.请求拥有条件:当一个线程拥有一个资源之后又试图请求其他资源。
3.不可剥夺条件:当一个资源被一个线程拥有后,如果不是这个线程主动释放资源,其他线程不可拥有此资源
4.环路等待条件:两个或两个以上的线程拥有资源后,试图获取对方资源而形成的一个环路
如何解决死锁的问题?
解决造成死锁的四个条件中的请求拥有条件和环路等待条件;
最有效的解决方案是控制加锁的顺序,解决环路等待条件;
wait()方法:
让线程进入休眠并释放资源。
不传参数或者参数为0时会一直等待被唤醒,此时状态是waiting
传入 大于0 的参数时,被指定时间内如果没被唤醒,将自己唤醒,休眠时状态为timed_waiting;
notify():
唤醒单个线程,不能指定唤醒某个线程。
notiftAll():
唤醒所有线程。
解决不能指定唤醒某个线程的问题:LockSupport park/unpark;
wait()注意事项:
使用前必须加锁,也就是说wait()一定要配合Synchronized使用;
wait()和notify()在配合synchronized使用时一定要使用同一把锁 wait()和notify()配合使用时,一定是同一把锁
wait()和 sleep()的区别
区别 | sleep() | wait() |
---|---|---|
使线程 休眠 | 是 | 是 |
能否接收线程终止通知 | 能 | 能 |
配合synchronized使用 | 不需要 | 需要 |
休眠时是否释放锁 | 是 | 否 |
是否可以主动唤醒线程 | 不能 | 能 |
方法所属类 | Thread | Object |
sleep(0)和 wait(0)有什么区别?
sleep(0)表示0庙后继续执行线程,wait(0)表示一直休眠
sleep(0)表示重新出发一次cpu的竞争。
为什么wait会释放锁?而sleep不会释放锁?
答: sleep必须要传递一个最大等待时间的,也说sleep是可控的(对于时间层面来讲),而 wait 是可以不传递传输,从设计层面来讲如果让 wait这个没有超时等待时间的机制不释放锁的话,那么线程可能会一直阻塞,而sleep 就不存在这个问题。
为什么wait是Object 的方法,而sleep是 Thread 的方法?
答: wait需要操作锁,而锁是属于对象级别(所有的锁都是放在对象头当中),它不是线程级别,一个线程中可以有多把锁,为了灵活起见,所以就将 wait放在0bject当中。
线程池
定义:利用池化技术去更好的管理线程和使用线程的方式。
线程池的两个重要对象:
1.线程
2.工作队列
为什么要用线程池?
线程的缺点:线程的创建会为线程开辟,本地方法栈,虚拟机栈,程序计数器的私有内存,线程的结束会消耗以上三个区域,频繁的创建和销毁会消耗系统的资源,并且线程不会友好的去拒绝完成不了的任务。
线程池的优点:
1.不用频繁的创建和销毁线程;
2.可以更好地控制线程个数和资源个数;
3.拥有更多的功能,比如去进行定时任务的执行;
4.可以对完成不了的线程进行友好的拒绝;
线程池的7种创建方式
1.创建固定线程个数的线程数
2.创建带有缓存的线程池
3.创建可以执行定时任务的线程池
4.创建单个线程执行定时任务的线程池
5.创建单个线程的线程池
6.根据当前cpu核数创建相应的线程数的线程池
7.原始的创建线程池的方式:ThreadPoolExecutor(如下代码)
public class ThreadPoolDemo55 {
static int count = 1;
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("myThreadPool-"+count++);
return thread;
}
};
//原始的创建线程池的方式
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(2,2,60,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),threadFactory);
for (int i = 0; i < 3; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
});
}
}
}
关于ThreadPoolExecutor():
第一个参数:线程池的核心线程数;
第二个参数:线程池的最大线程数;
第三个参数:最大线程数存活时间;
第四个参数:最大存活时间的时间单位;
第五个参数:设置工作队列的大小;
第六个参数:线程工厂
第七个参数:进行拒绝策略的设置
ThreadPoolExecutor()的执行流程:
ThreadPoolExecutor()的五种拒绝策略:
1.ThreadPoolExecutor.AbortPolicy(默认拒绝策略,不执行任务时抛出异常)
2.ThreadPoolExecutor.callerRunsPolicy(交给主线程去执行任务)
3.ThreadPoolExecutor Discard0ldestPolicy(丢弃最老的任务)
4.ThreadPoolExecutor IDiscardPolicy (丢弃最新的任务)
5.自定义拒绝策略(可以写到日志里,存储到数据库,也可以什么都不干)
线程池的终止:
1.shutDown():等待任务执行完关闭;
2.shutDownNow():立即关闭,任务不会执行完;
线程池的状态:
单例模式:
全局唯一并且所有程序都可以使用的对象,就是单例模式。
下面两种方式进行实现。
1.饿汉模式:
public class Singleton {
private Singleton() {
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
2.懒汉模式(最终版):
public class Singleton {
private Singleton() {
}
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
为什么懒汉模式要加上volatile
cpu实例化对象分为三个步骤:
1.先分配内存空间;
2.初始化;
3.将instance对象指向内存;
加上volatile就防止了指令重排,防止线程拿到一个未初始化过的对象进行返回,而造成线程不安全;
ThreadLocal
ThreadLocal的经典使用场景
1.解决线程不安全的问题;
2.线程级的数据传递;
选择ThreadLocal还是锁?
看创建实例对象之后的复用率,复用率高就用ThreadLocal;
ThreadLocal的初始化方法在什么时候才会执行?
当ThreadLocal中出现set方法之后,初始化方法将不会在执行;
当ThreadLocal在使用get方法时,才会判断并调用初始化方法;
ThreadLocal的缺点?
1.不可继承:ThreadLocal是不可继承的,这是使用它的一个子类InheritableThreadLocal可以解决;
2.会出现脏读;
3.(重要):内存泄露问题:
泄露原因:
ThreadLocal的存储是在Thread下的一个ThreadLocalMap中以键值对的形式进行存储:key被定义成弱引用,value被定义为强引用,在垃圾回收的时候,key会被清理掉,而value不会,则这导致ThreadLocalMap中用很多的 key为null的entry,如若我们不做任何处理的话,value永远不会被处理掉,此时就发生了内存泄漏,解决方案很简单,使用ThreadLocal下的remove方法及时的删除就可以避免内存泄漏;
java引用的四种类型
1.强引用:即使发生OOM(内存溢出)也不会进行回收;
2.软引用:在内存足够的情况下,垃圾回收器可以考虑不回收此引用,当OOM时才会回收;
3.弱引用:不管内存是否足够,都要进行回收;
4.虚引用:创建即回收,可以触发一个垃圾回收的回调;