单核CPU下,多线程不能实际提高程序运行效率,值是为了能够在不同的任务之间切换。
多核CPU可以并行跑多个线程,可以提高程序运行效率。
java中创建线程的方式
Thread类的方式
Thread t=new Thread(){
public void run(){
//要执行的任务
}
};
t.start();
Runnable 的方式
Runnable r= () -> System.out.println("进入线程Runnable");
Thread是吧线程和任务合并在一起
Runnable 是吧线程和任务分开了。
用Runnable 让任务类脱离了Thread继承体系,更加灵活。
FutureTask配合Thread
FutureTask源代码内部也是继承了Runnable 接口,不过他存在Future的线程执行返回结果。
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println("执行Callable");
Thread.sleep(100);
return 100;
}
});
Thread t=new Thread(task,"cal1");
t.start();
//执行task的get方法会等待线程执行完成之后。获取返回结果
System.out.println(task.get());
}
java项目在linux中查看进程的方式,命令如下:
ps -fe || grep java
jps
linux查看线程方式
top -h -p 线程ID
jstack 线程ID
也可以使用jconsole远程连接监控线程
线程运行的原理:
栈和栈帧
栈内存是由多个栈帧组成。
栈内存的方式是 先进后出。
创建线程需要分配时间调度切换,开启线程和关闭线程
线程池的线程在于开启线程以后不会释放线程栈空间。
线程是稀缺资源,他是创建与销毁是一个相对偏重且消耗资源的操作,而java线程依赖于内核线程,要进行操作系统状态切换
为避免资源过度消耗需要设法重用线程执行多个任务,线程池就是一个负责对线程进行统一分配。调度与监控
线程池工具类Executor
ExecutorService service=new newCachedThreadPool();
service.execute(Runnable任务);
newCachedThreadPool 最快 有多少任务会创建多少线程,线程过多会让CPU达到100%
newFixedThreadPool 较慢慢 创建线程数是传入的数量 ,只能执行固定的线程数量,多余的任务存放在队列中,任务过多会出现内存溢出
newSingleThreadExecutor 最慢 只会创建一个线程。多余的任务存放在队列中,任务过多会出现内存溢出
不推荐使用上面现有的创建线程池方式,下面是使用自定义线程池方式:
ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor('核心线程数','最大线程数','时间策略','等待队列')
线程池处理任务顺序:
核心线程
队列排队
非核心线程
都满了,触发拒绝策略
redis实现分布式锁:
命令
setnx key value
值在键key不存在的情况下,将键key的值设置为value
使用redis锁要注意使用finally释放锁
需要设置超时时间,但是设置超时时间的时候需要保证存锁的过程中是一起执行的。
由于设置了超时时间,这时候会出现时间到了代码没执行完,另外线程B进来了。然后线程A执行了finally释放了线程B的锁
所以这时候需要在value里面放入在这个线程生成的uuid,释放锁定的时候判断是否uuid是否还是一样的。
设置超时时间,可以加锁以后,开辟一个线程,给锁重置时间。不过这里开辟线程存在较多的问题。
这时候我们可以直接引用redisson工具包进行加锁。
如果要对锁提高性能,对库存进行分段存储,分段锁。比如1个商品100数量
可以拆分成10个商品每个商品10个数量
native关键字
凡是带来native关键字的,说明java的作用达不到了,去调用底层C语言库
会进入本地方法栈
调用本地方法本地栈jni
JNI作用:扩展java的作用,融合不同的编程语言为java所用。
作用:volatile关键字,改共享变量的可见性
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主内存,当有其他线程读取时,它会去内存中读取。
通过缓存一致性协议。每个数据存在一种状态。
在jmm内存模型中划分为:
主内存-》缓存内存->工作内存
当我们创建一个普通变量时,这个变量放在主内存中。使用过程中会放在缓存内存中。
当开启一个线程进行时候的时候,会将变量复制一份,放在自己的工作内存中执行。
这时候如果现在A修改了加了volatile的变量,会通过监听机制,广播其他线程,告诉他们我进行了修改。
其他线程就会将自己工作线程中的变量,状态修改为失效状态。再次使用的时候,发现已经失效,会从新去主内存中查询。
volatile能防止指令重排,但是不能保证原子性
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。
AQS
大部分同步锁是基于AQS实现的。是JDK提供的一个同步框架,
内部维护着一个FIFO双向队列,即CLH同步队列
AQS依赖它的同步管理
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程