多线程和JUC
进程
进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有他自己的内存空间和系统资源
多进程意义在于计算机可以执行多个任务,提高cpu使用率
我们在一边玩游戏,一边听音乐的时候,是cpu在做着程序间的高效切换让我们觉得是同时进行的
注意:很多多线程是模拟出来的,真正的多线程是值有多个cpu,即多核,如服务器。 如果是模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉
线程
线程是依赖于进程而存在的,在一个进程内又可以执行多个任务,而这每个任务我就可以看出是一个线程
线程:是程序的执行单元,执行路径。是程序使用cpu的最基本单位。
单线程:程序只有一条执行路径
多线程:程序有多条执行路径
多线程意义在于提高应用程序的使用率。不是提高程序的执行速度
程序的执行其实都是在抢cpu的资源,cpu的执行权
多个进程是抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到cpu的执行权,所以线程的执行有随机性
注意:
并行和并发的区别:
前者:逻辑上同时发生,指在某一个时间段同时运行多个程序
CPU多核,多个线程可以同时执行线程池
后者:物理上同时发生,指在某一个时间点同时运行多个程序
CPU单核,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉
并发编程本质就是充分利用CPU的资源
java程序的运行原理
由java命令启动jvm,jvm启动就相当于启动了一个进程,接着有这个该进程创建了一个主线程去调用main()方法
那么jvm虚拟机的启动是多线程,因为垃圾回收线程也要先启动,否则很容易出现内存溢出。主线程加垃圾回收线程
小结:
线程就是独立的执行路径;
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
main()成为主线程,为系统的入口,用于执行整个程序;
在一个进程中,如果开辟了多个线程,线程的运行由调度器(CPU)安排调度,调度器是与操作系统紧密相关,先后顺序是不能人为的干预的;
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
线程会带来额外的开销,如CPU调度时间,并发控制开销;
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;
如何实现多线程的程序?
继承Thread类:
子类继承Thread类具备多线程能力
启动线程:子类对象.start()方法
不建议使用:避免OOP单继承局限性
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class ThrendDemo extends Thread{
//src="http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20191101021614.png"
//src="http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20201015024451.png"
//src="http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20190902094518.png"
String url;
String name;
public ThrendDemo(String url,String name){
this.url=url;
this.name=name;
}
@Override
public void run() {
DownWeb downWeb = new DownWeb();
downWeb.downLoader(url,name);
System.out.println("下载了文件名为"+name);
}
public static void main(String[] args) {
ThrendDemo th1 = new ThrendDemo("http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20191101021614.png","1.jpg");
ThrendDemo th2 = new ThrendDemo("http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20201015024451.png","2.jpg");
ThrendDemo th3 = new ThrendDemo("http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20190902094518.png","3.jpg");
th1.start();
th2.start();
th3.start();
}
}
class DownWeb{
public void downLoader(String url,String name) {
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,下载失败");
}
}
}
另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动
实现接口方式的好处
可以避免由于java单继承带来的局限性;适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想
为什么要重写run()方法?
run()里面封装的是被线程执行的代码
启动线程对象用的是哪个方法?
start()
run()和start()方法的区别?
run()直接调用的仅仅是普通方法
start()先启动线程,再由jvm调用run()
实现接口Runnable具有多线程能力
启动线程:new Thread(传入目标对象).start()
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class ThrendDemo implements Runnable{
String url;
String name;
public ThrendDemo(String url,String name){
this.url=url;
this.name=name;
}
@Override
public void run() {
DownWeb downWeb = new DownWeb();
downWeb.downLoader(url,name);
System.out.println("下载了文件名为"+name);
}
public static void main(String[] args) {
ThrendDemo th1 = new ThrendDemo("http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20200528052009.png","4.jpg");
ThrendDemo th2 = new ThrendDemo("http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20191105020544.png","5.jpg");
ThrendDemo th3 = new ThrendDemo("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1603600907&di=62c16889d83b7e6742012de55df48d04&src=http://meitu.qqzong.com/www.52520qq.com/uploads/allimg/181106/003G51110-0.jpg","6.jpg");
// th1.start();
// th2.start();
// th3.start();
new Thread(th1).start();
new Thread(th2).start();
new Thread(th3).start();
}
}
class DownWeb{
public void downLoader(String url,String name) {
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,下载失败");
}
}
}
实现Callable接口
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务:
5.提交执行
6.获取结果
7.关闭服务
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class ThrendDemo implements Callable<Boolean> {
String url;
String name;
public ThrendDemo(String url,String name){
this.url=url;
this.name=name;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThrendDemo th1 = new ThrendDemo("http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20200528052009.png","4.jpg");
ThrendDemo th2 = new ThrendDemo("http://iflyssedata.oss-cn-shanghai.aliyuncs.com/images/20191105020544.png","5.jpg");
ThrendDemo th3 = new ThrendDemo("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1603600907&di=62c16889d83b7e6742012de55df48d04&src=http://meitu.qqzong.com/www.52520qq.com/uploads/allimg/181106/003G51110-0.jpg","6.jpg");
// 4.创建执行服务:
ExecutorService executorService =Executors.newFixedThreadPool(3);
// 5.提交执行
Future<Boolean> future1= executorService.submit(th1);
Future<Boolean> future2= executorService.submit(th2);
Future<Boolean> future3= executorService.submit(th3);
// 6.获取结果
future1.get();
future2.get();
future3.get();
// 7.关闭服务
executorService.shutdownNow();
}
@Override
public Boolean call() throws Exception {
DownWeb downWeb = new DownWeb();
downWeb.downLoader(url,name);
System.out.println("下载文件名为"+name);
return true;
}
}
class DownWeb{
public void downLoader(String url,String name) {
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,下载失败");
}
}
}
由于线程是依赖进程而存在,所以应该先创建一个进程出来,而进程是系统创建的,所以我们应该去调用系统功能创建一个进程。java是不能直接调用系统功能的,依赖于c/c++去调用系统功能创建进程,然后java去调用这样的东西
(Unsafe类 java无法操作内存,java可以调用c++ native c++可以操作内存)
(1)如何设置线程对象的名称?
public final String getName();获取线程的名称
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<200;i++) {
System.out.println(getName()+"点赞"+i);
}
}
Thread-0点赞0
Thread-0点赞1
Thread-0点赞2
Thread-0点赞3
Thread-0点赞4
Thread-1点赞0
(2)如何设置线程对象的名称
public final void setName(String name)
//默认无参构造+setName()来设置线程名称
// //创建两个线程对象
// MyThreadDemo myThreadDemo1 = new MyThreadDemo();
// MyThreadDemo myThreadDemo2 = new MyThreadDemo();
// //设置线程名称
myThreadDemo1.setName("线程一");
myThreadDemo2.setName("线程二");
// //线程启动
// myThreadDemo1.start();
// myThreadDemo2.start();
//通过有参构造来设置线程名称
MyThreadDemo myThreadDemo3 =new MyThreadDemo("线程三");
MyThreadDemo myThreadDemo4 = new MyThreadDemo("线程四");
myThreadDemo3.start();
myThreadDemo4.start();
线程二点赞9
线程一点赞0
线程一点赞1
线程一点赞2
线程一点赞3
针对不是Thread类的子类中来获取线程对象的名称?
public static Thread currentThread()
Thread.currentThread().getName()
(3)线程有个默认优先级就是5
如何设置优先级?(1最低优先级,10最高优先级,优先级用数字表示,范围从1~10)
java使用的是抢占式调度模型 优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的cpu时间片相对多一些
线程优先级高仅仅表示线程获取的cpu时间片的几率高,但是要在运行次数多的时候才会看到比较好的效果
public final int getPriority();返回线程对象的优先级
public final void setPriority(int newPriority)//更改线程的优先级
**注意:优先级的设定建议在start()调度前;优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看cpu的调度
**
public class ThreadPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
public static void main(String[] args) {
ThreadPriority threadPriority = new ThreadPriority();
Thread thread1 = new Thread(threadPriority);
Thread thread2 = new Thread(threadPriority);
Thread thread3 = new Thread(threadPriority);
Thread thread4= new Thread(threadPriority);
Thread thread5= new Thread(threadPriority);
Thread thread6 = new Thread(threadPriority);
thread1.setPriority(8);
thread1.start();
thread2.setPriority(5);
thread2.start();
thread4.setPriority(7);
thread4.start();
thread6.setPriority(Thread.MAX_PRIORITY);
thread6.start();
}
}
线程的休眠
public static void sleep(long mills)毫秒数
时间达到后线程进入就绪状态;
可以模拟网络延时(放大问题的发生性),倒计时等;
每一个对象都有一个锁,sleep不会释放锁;
线程的加入
public final void join()
Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞;
可以想象成插队
public class ThreadJoin implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("线程-->"+i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadJoin threadJoin = new ThreadJoin();
Thread t = new Thread(threadJoin);
t.start();
for(int i=0;i<20;i++){
System.out.println("主线程-->"+i);
if(i==10){
t.join();
}
}
}
}
线程礼让
public static void yield()
让多个线程执行和谐一点,但是不能保证一人一次
让当前正在执行的线程暂停,但不阻塞;
将线程从运行状态转化为就绪状态;
让cpu重新调度,礼让不一定成功,看cpu心情
后台线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕;
虚拟机不用等待守护线程执行完毕;
如:后台记录操作日志,监控内存,垃圾回收等待
public final void setDaemon(boolean on)线程启动前必须调用此方法。
public class TestDaemon {
public static void main(String[] args) {
he h = new he();
Thread thread = new Thread(h);
thread.setDaemon(true);
thread.start();
new Thread(new my()).start();
}
}
class my implements Runnable{
@Override
public void run() {
for (int i = 0; i < 8; i++) {
System.out.println("自己的线程");
}
}
}
class he implements Runnable{
@Override
public void run() {
while(true){
System.out.println("守护线程");
}
}
}
中断线程
public final void stop():已过时,比较暴力
public void interrupt():中断线程,把线程的状态终止,并抛出一个InterruptedException
如何让线程停止?(设置标志位)
public class ThreadStop implements Runnable{
boolean flage = true;
@Override
public void run() {
int i=0;
while(flage){
System.out.println(Thread.currentThread().getName()+"-->"+(i++));
}
}
public void StopThread(){
this.flage = false;
}
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
new Thread(threadStop).start();
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+i);
if(i==10){
threadStop.StopThread();
System.out.println("线程停止了");
}
}
}
}
多线程实例
并发:同一个对象被多个线程同时操作
线程同步:
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下问切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
举例:100张票,3个售票窗口,设计一个程序模拟电影院售票
会出现哪些问题:相同的票出现多次:
cpu的一次操作必须是原子性
出现负数的票:
随机性和延迟导致的
注意:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响比较大
引起一个程序是否有线程安全问题的标准?
a.是否多线程环境
b.是否有共享数据
c.是否有多条语句操作共享数据
满足这些条件,会出现线程安全问题,如何解决?
同步机制(同步代码块:)
synchronized(Obj对象){
需要同步的代码;(多条语句操作共享数据的代码块)
}
Obj称为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this(对象本身或者class)
同步监视器的执行过程:
1.第一个线程访问,锁定同步监视器,执行其中代码
2.第二个线程访问,发现同步监视器被锁定,无法访问
3.第一个线程访问完毕,解锁同步监视器
4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问
注意:同步可以解决线程安全问题的根本原因就在那个对象上。该对象如同锁的功能,多个线程必须是同一把锁
public static void main(String[] args) throws InterruptedException {
List<String > list = new ArrayList<String>();
for(int i=0;i<10000;i++){
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
10000
虽然同步解决了多线程的安全问题,但是每当线程相当多的时候,每个线程都会去判断同步上的锁,耗资源,降低了程序运行的效率
同步代码块的锁及同步方法应用锁的问题:
同步代码块的所对象是谁?
任意对象(锁的对象就是变化的量,需要增删改)
同步方法的格式及锁对象问题?
把同步关键字加在方法上
同步方法是this
静态方法及锁对象问题?
静态方法的锁对象是谁类的字节码文件对象
为了更清晰的表达如何加锁和释放锁,用:
Lock:
void lock()//加锁
void unlock();//释放锁
ReentrantLock是实现Lockde 实现类
综合代码如下:
public class MyThread implements Runnable{
private int toe = 100;
//定义锁
private Lock lock= new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
lock.lock();
if(toe>0) {
try {
Thread.sleep(100); System.out.println(Thread.currentThread().getName()+"正在出售"+(toe--)+"票");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread th1 = new Thread(myThread, "窗口一");
Thread th2 = new Thread(myThread, "窗口二");
Thread th3 = new Thread(myThread, "窗口三");
th1.start();
th2.start();
th3.start();
}
}
死锁问题
同步弊端:效率低,如果出现了同步嵌套,就容易产生死锁问题
**死锁:**是指多个线程在执行的过程中,因争夺资源产生的一种互相等待现象
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步代码块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
死锁的四个必要条件:
1、互斥条件:一个资源每次只能被一个进程使用
2、请求与保持条件:一个进程因请求资源而被阻塞时,对方已获得的资源保持不放
3、不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
synchronized与lock的对比:
1、Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
2、Lock只有代码块锁,synchronized有代码块锁和方法锁
3、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
4、优先级使用顺序:
Lock>同步代码块(已经进入方法体,分配了相应资源)>同步方法(在方法体之外)
死锁排查
public class DeadLock {
public static void main(String[] args) {
new Thread(new MyThread("la","lb")).start();
new Thread(new MyThread("lb","la")).start();
}
}
class MyThread implements Runnable{
String lockA;
String lockB;
public MyThread(String lockA,String lockB){
this.lockA=lockA;
this.lockB=lockB;
}
@Override
public void run() {
synchronized(lockA){
System.out.println(Thread.currentThread().getName()+"A去拿B的资源");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"B去拿A的资源");
}
}
}
}
jps -l来查看进程号:
5328 sun.tools.jps.Jps
16296
5640 com.qianjiang.item.lock.DeadLock
14620 org.jetbrains.jps.cmdline.Launcher
jstack 进程号 来查看具体信息
"Thread-1" #13 prio=5 os_prio=0 tid=0x000000001a4fa800 nid=0x22c8 waiting for monitor entry [0x000000001b2ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.qianjiang.item.lock.MyThread.run(DeadLock.java:31)
- waiting to lock <0x00000000d60c1358> (a java.lang.String)
- locked <0x00000000d60c1388> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #12 prio=5 os_prio=0 tid=0x000000001a4f7800 nid=0x2860 waiting for monitor entry [0x000000001b1ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.qianjiang.item.lock.MyThread.run(DeadLock.java:31)
- waiting to lock <0x00000000d60c1388> (a java.lang.String)
- locked <0x00000000d60c1358> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
排查问题:
1、日志信息
2、堆栈信息
生产者和消费者问题(线程协作)
cpu的一点点时间片的执行权,就足够执行很多次
线程运行的随机性
这样出现了线程安全问题,解决它用加锁
加锁注意的是不同种类的线程都要加锁,不同种类的线程加的锁必须是同一把(操作的是同一个对象)
这样线程安全解决了,会存在着如下问题:
a.假设消费者先抢到cpu的执行权,就会去消费数据,但是现在的数据时没有或者默认值,无意义,等着生产者去生产数据,再消费
b.假设生产者先抢到cpu的执行权,就会去产生数据,产生完成,继续产生数据是有问题,应该等着消费者把数据消费掉,然后在生产
生产者:
先看是否有数据,有就等待,没有就生产,生产完之后通知消费者来消费
消费者:
先看是否有数据,有就消费,没有就等待通知生产者生产数据
在生产者消费者问题中,仅有synchronized是不够的:
synchronized可阻止并发更新同一个共享资源,实现了同步
synchronized不能用来实现不同线程之间的消息传递(通信)
解决这个问题,java就提供了一种机制,等待唤醒机制
Object类中提供了三个方法:
wait();等待,从这里等待就从这里醒来,等待过程就立即释放锁了与sleep不同,会释放锁
notify():唤醒单个进程,并不代表立马可以执行 ,必须还得抢占cpu的执行权
notifyAll():唤醒所有线程
为什么这些方法存在Object类中而不是Thread类中?
这些方法的调用必须通过锁对象,而使用的锁对象是任意锁对象
线程组
就是把多个线程组合在一起,可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
默认情况下:所有的线程都属于同一个组
ThreadGroup threadGroup = new ThreadGroup("这是一个线程组");
ThreadGroupDemo threadGroupDemo = new ThreadGroupDemo();
Thread t1 = new Thread(threadGroup,threadGroupDemo,"线程一");
Thread t2= new Thread(threadGroup,threadGroupDemo,"线程二");
System.out.println(t1.getName());//线程一
System.out.println(t2.getName());//线程二
System.out.println(threadGroup.getName());//这是一个线程组
线程池
程序启动一个新的线程成本计较高,因为它涉及要与操作系统交互。线程池可以很好地提高性能,尤其是当程序中要创建大量生存期很短的线程时,考虑使用线程池
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用,从jdk5开始,java内置支持线程池
创建新的线程池:public static ExecutorService newFixedThreadPool(int nThreads)
调用方法:
Future<?> submit(Runnable task)提交一个可运行的任务执行,并返回一个表示该任务的未来
Future submit(Callable task)
结束:
void shutdown()启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 如果已经关闭,调用没有额外的作用。
此方法不等待以前提交的任务完成执行。 使用awaitTermination做到这一点。
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
ThreadGroupDemo myThread = new ThreadGroupDemo();
ThreadGroupDemo myThread1 = new ThreadGroupDemo();
Future<?> submit = newFixedThreadPool.submit(myThread);
Future<?> submit2 = newFixedThreadPool.submit(myThread1);
newFixedThreadPool.shutdown();
定时器
是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台的线程方式执行
Time类:
void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。
void schedule(TimerTask task, long delay, long period) 在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
TimeTask类:
boolean cancel()
取消此计时器任务。
abstract void run()
该定时器任务要执行的操作。
开发中Quartz是一个完全由java编写的开源框架
public class TimerDemo {
public static void main(String[] args) {
//创建一个定时器
Timer time = new Timer();
//开启定时器做任务
System.out.println(new Date());
time.schedule(new myTask(time), 8000);
}
}
class myTask extends TimerTask{
Timer time;
public myTask(Timer time) {
this.time=time;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("炸弹响起来!!");
time.cancel();
System.out.println(new Date());
}
}
* 在指定的时间删除我们指定目录
*/
public class DeleteFolder extends TimerTask{
Timer t;
public DeleteFolder(Timer t) {
// TODO Auto-generated constructor stub
this.t = t;
}
@Override
public void run() {
// TODO Auto-generated method stub
File file = new File("demo");
DeleteFile(file);
t.cancel();
}
private void DeleteFile(File file) {
// TODO Auto-generated method stub
//用递归方法删除目录
File[] listFiles = file.listFiles();
if(listFiles!=null) {
for(File f:listFiles) {
if(f.isDirectory()) {
DeleteFile(f);
}else {
System.out.println(f.getName()+":"+f.delete());
}
}
System.out.println(file.getName()+":"+file.delete());
}
}
}
public class MyTimer {
public static void main(String[] args) throws Exception {
Timer t = new Timer();
String s= "2020-9-17 10:22:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = simpleDateFormat.parse(s);
t.schedule(new DeleteFolder(t), d);
}
}
以前线程安全类的回顾
StringBuffer sb = new StringBuffer();
Vector<String> v = new Vector<String>();
Hashtable<String,String> ht = new Hashtable<String,String>();
Vector是线程安全的时候才去考虑他,也可以不用
用public static<T> List<T> synchronizeList(List<T> list)
List<String> list1 = new ArrayList<String>();//线程不安全
List<String> list2 = Collections.synchronizedList(new ArrayList<String>())
线程的生命周期
新建:创建线程对象
就绪:有执行资格,没有执行权(调用start方法,线程立即进入就绪状态,但不意味着立即调度执行)
运行:有执行资格,有执行权(线程才真正执行线程体的代码块 )
阻塞:由于一些操作让线程处于该状态,没有执行资格,没有执行权
而另一些操作却可以把它给激活,激活后处于就绪状态(当调用sleep,wait或者同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行)
死亡:线程对象变成垃圾,等待被回收,中断或者结束 就不能再次启动了
public class ThreadStat {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("-------");
});
Thread.State s = thread.getState();
System.out.println(s);//NEW
//启动线程
thread.start();
s=thread.getState();
System.out.println(s);//RUNNABLE
//阻塞状态
while(s!=Thread.State.TERMINATED){//线程不终止
Thread.sleep(1000);
s=thread.getState();
System.out.println( s);
}
}
}
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
RUNNABLE
TIMED_WAITING
RUNNABLE
-------
TERMINATED
JUC的学习
常用工具类:
java.util.concurrent.CyclicBarrier
允许一组线程全部等待彼此达到共同屏障点的同步辅助(加法计数器)
public class CyclicBarrierDemo {
public static void main(String[] args) {
int parties=5;
CyclicBarrier cyclicBarrier = new CyclicBarrier(parties,()->{
System.out.println("恭喜集全了"+parties+"福");
});
for(int i=1;i<=parties;i++){
int temp = i;
new Thread(()->{
System.out.println("线程"+Thread.currentThread().getName()+"-->集"+temp+"福");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
java.util.concurrent.CountDownLatch
一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开
void countDown()
减少锁存器的计数,如果计数达到零,释放所有等待的线程。
void await() //等待计数器归零
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int count=6;
CountDownLatch countDownLatch = new CountDownLatch(count);
for (int i=1;i<=count;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"-->go out");
countDownLatch.countDown();//数量减一
}).start();
}
countDownLatch.await();//等数量归零,继续执行下一步
System.out.println("close door");
}
}
Thread-0-->go out
Thread-2-->go out
Thread-1-->go out
Thread-3-->go out
Thread-5-->go out
Thread-4-->go out
close door
java.util.concurrent.Semaphore
一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方
public void acquire()
throws InterruptedException从此信号量获取许可证
public void release()释放许可证,将其返回到信号量。
public class SemaphoreDemo {
public static void main(String[] args) {
//六个车位 抢占三个车位 限流
int permits=3;
Semaphore semaphore = new Semaphore(3);
for(int i=1;i<=7;i++){
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢占了车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
Thread-1抢占了车位
Thread-2抢占了车位
Thread-0抢占了车位
Thread-2离开了车位
Thread-0离开了车位
Thread-3抢占了车位
Thread-1离开了车位
Thread-4抢占了车位
Thread-5抢占了车位
Thread-3离开了车位
Thread-6抢占了车位
Thread-5离开了车位
Thread-4离开了车位
Thread-6离开了车位
semaphore.acquire();获得 假设如果已经满了,等待,等待被释放为止
semaphore.release();释放,会将当前的信号量释放+1.然后唤醒等待的线程
作用:多个共享资源互斥的使用,并发限流,控制最大的线程数
读写锁
ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入,所以:
读 读:可以共存
读 写 :不能共存
写 写:不能共存
Lock readLock()
返回用于阅读的锁。
Lock writeLock()
返回用于写入的锁。
独占锁(写锁):一次只能被一个线程占有
共享锁(共享锁):多个线程可以同时占有
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写
for(int i=0;i<5;i++){
new Thread(()->{
myCache.writer();
}).start();
}
//读
for(int i=0;i<2;i++){
new Thread(()->{
myCache.reader();
}).start();
}
}
}
class MyCache{
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//写
public void writer(){
try{
readWriteLock.writeLock().lock(); System.out.println(Thread.currentThread().getName()+"--->开始写入");
System.out.println("-----------写写写------"); System.out.println(Thread.currentThread().getName()+"-->写入完成");
}finally {
readWriteLock.writeLock().unlock();
}
}
//读
public void reader(){
try{
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"--->开始读");
System.out.println("-----------读读读------");
System.out.println(Thread.currentThread().getName()+"--->读出完成");
}finally {
readWriteLock.readLock().unlock();
}
}
}
(阻塞队列)BlockingDeque
这个队列排列元素FIFO(先进先出)。
public ArrayBlockingQueue(int capacity)创建具有给定(固定)容量capacity - 这个队列的容量
有返回值,不抛出异常
boolean offer(E e)入列
E poll()出列deque为空,则返回null
public class BlockingDequeDemo {
public static void main(String[] args) {
ArrayBlockingQueue blockingDeque = new ArrayBlockingQueue(2);
System.out.println( blockingDeque.offer("A"));//true
System.out.println(blockingDeque.offer("B"));//true
System.out.println(blockingDeque.offer("C"));//false
System.out.println(blockingDeque.poll());//A
System.out.println(blockingDeque.poll());//B
System.out.println(blockingDeque.poll());//null
}
}
超时等待
public boolean offer(E e,
long timeout,
TimeUnit unit)
throws InterruptedException在该队列的尾部插入指定的元素,等待指定的等待时间,以使空间在队列已满时变为可用。
e - 要添加的元素
timeout - 具体时间 (等待多久程序结束)
unit :时间单位
public class BlockingDequeDemo {
public static void main(String[] args) throws InterruptedException {
int capacity=2;
ArrayBlockingQueue blockingDeque = new ArrayBlockingQueue(2);
blockingDeque.offer("A");
blockingDeque.offer("B");
//blockingDeque.offer("C",3, TimeUnit.SECONDS);//三秒之后继续执行
System.out.println("-------------");
System.out.println(blockingDeque.poll());
System.out.println(blockingDeque.poll());
blockingDeque.poll(3,TimeUnit.SECONDS);
}
}
一直等待
public void put(E e)
throws InterruptedException在该队列的尾部插入指定的元素,如果队列已满,则等待空间变为可用。存入元素,超过最大容量就一直都等待
public E take()
throws InterruptedException取出元素,对列中没有就一直等待
public class BlockingDequeDemo {
public static void main(String[] args) throws InterruptedException {
int capacity=2;
ArrayBlockingQueue blockingDeque = new ArrayBlockingQueue(2);
blockingDeque.put("A");
blockingDeque.put("B");
//blockingDeque.put("c");
System.out.println("----------------");
System.out.println(blockingDeque.take());
System.out.println(blockingDeque.take());
blockingDeque.take();
}
}
SynchronousQueue(同步队列)
和BlockingDeque不一样,不保证容量大小。只放(put)一个值,并且放了就需要取(take)出来
void put(E e)
将指定的元素添加到此队列,等待另一个线程接收它。
E take() 取
线程池
Executor
线程池:三大方法 7大参数 4种拒绝策略
程序的运行 本质:占用系统的资源
为了优化资源的使用 引入线程池技术(池化技术)池化技术:事先准备好资源 用完了 在还给我
线程池、连接池、内存池、对象池。。。
线程池的好处:
*降低资源的消耗
*提高响应的速度
*方便管理
线程管理 可以控制最大并发数 管理线程
public static void main(String[] args) {
SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue();
new Thread(()->{
try {
synchronousQueue.put(23);
System.out.println(Thread.currentThread().getName()+"put 23");
synchronousQueue.put(13);
System.out.println(Thread.currentThread().getName()+"put 13");
synchronousQueue.put(93);
System.out.println(Thread.currentThread().getName()+"put 93");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程一").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"-->take"+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"-->take"+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"-->take"+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程二").start();
}
public static void main(String[] args) {
//ExecutorService pool = Executors.newSingleThreadExecutor();//单个线程池
//ExecutorService pools = Executors.newFixedThreadPool(5);//多个线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
try{
for(int i=0;i<10;i++){
//线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
}finally {
//关闭线程池
threadPool.shutdown();
}
}
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
ThreadPoolExecutor的7大参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心线程池大小
maxiumPoolSize:最大核心线程池大小
keepAliveTime:超时了没有调用就会释放
unit:超时单位
BlockingQueue:阻塞队列
threadFactory:线程工厂,创建线程的,一般不动
handle:拒绝策略
ThreadPoolExecutor四种拒绝策略
AbortPolicy:corePoolSize 满 BlockingQueue满 同时MaxiumPoolSize也用上了 (对列满了 如果还有其他人进来 不处理这个人了,抛出异常)
CallerRunsPolicy:队列满了 main线程有时会处理 不抛出异常
DiscardOldestPolicy :队列满了,尝试着处理,不会抛出异常
DiscardPolicy:队列满了 (有时候运行多了随缘处理)不处理进来的人,不会抛出异常
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
try{
//最大承载量 max+capacity(8)
for(int i=1;i<=12;i++){
//线程池创建线程
threadPoolExecutor .execute(()->{ System.out.println(Thread.currentThread().getName()+" ok");
});
}
}finally {
//关闭线程池
threadPoolExecutor .shutdown();
}
}
}
maxiumPoolSize:最大核心线程池大小怎么定义?
1、CPU密集型 几核就定义几核,可以保持cpu的效率最高
怎么知道电脑是几核?
System.out.println(Runtime.getRuntime().availableProcessors());
2.io密集型:
设置大于程序中十分耗io的线程
ForkJoin
分治编程
它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。
如何使用forkjoin
1、forkjoinpool通过它执行
2、计算任务forkjoinpool.execute(ForkjoinTask task)
3、计算类要继承RecursiveTask
分治思想策略类:
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
private Long temp = 100000L;
public ForkJoinDemo(Long start,Long end){
this.start =start;
this.end=end;
}
@Override
protected Long compute() {
//小于临界值 用普通方式求和
if((end-start)<temp){
Long sum =0L;
for(Long i=start;i<=end;i++){
sum+=i;
}
return sum;
}else{
long middle = (start+end)/2;
ForkJoinDemo task1 = new ForkJoinDemo(start,middle);
task1.fork();//拆分了任务,八任务压人线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1,end);
task2.fork();
long result = task1.join()+task2.join();//对结果的统计
return result;
}
}
}
测试类:
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test();//执行时间:8489
test2();//执行时间:249
}
//使用Stream并行流
private static void test2() {
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L,10_0000_0000).parallel().reduce(0,Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum"+sum+"执行时间:"+(end-start));
}
//使用forkjoin
private static void test() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinTask<Long> task = new ForkJoinDemo(0L,10_0000_0000L);
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//这样会足阻塞等待
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum"+sum+"执行时间:"+(end-start));
}
}
异步回调
Future
java.util.concurrent.CompletableFuture
异步方法的依赖完成提供的操作
public static void main(String[] args) throws ExecutionException, InterruptedException {
//类比于ajax中的成功和失败的回调
//成功返回成功的信息,错误返回错误的信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName());
int i= 10/0;
return 1024;
});
Integer completableFuture1 = completableFuture.whenComplete((t, u)->{
System.out.println("t->"+t);//没错误,返回了成功的信息;有错误返回null
System.out.println("u->"+u);//没错误返回null,有错误返回错误的信息(java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero)
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 233;
}).get();
System.out.println(completableFuture1);//233
}
JMM
jmm:java内存模型 不存在的东西,一种约定而已
关于jmm的一些同步的约定:
1、线程解锁前,必须把共享变量刷回主存
2、线程加锁前,必须读取主存中的最新值到工作内存中
3、加锁和解锁是同一把锁
问题:线程B修改了值,但是线程A不能及时可见
private static int num =0;
public static void main(String[] args) {
new Thread(()->{
while(num==0){//这条线程对主存的变化不知道
}
}).start();
int num=1;
System.out.println(num);
}
与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:
lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:
(1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
(2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
(3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
(4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
(5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
(7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。
Volatile
就是因为在jmm中问题:线程B修改了值,但是线程A不能及时可见,所以引入volatile机制
volatile是java虚拟机提供的轻量级同步机制
1、保证可见性
private volatile static int num =0;//加了volatile时保证了可见性
public static void main(String[] args) {
new Thread(()->{//这个时候不会陷入死循环的
while(num==0){
}
}).start();
num=1;
System.out.println(num);
}
2、不保证原子性
那怎么保证原子性呢?(synchronized lock)
java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileDemo {
//private volatile static int num = 0;
// volatile 不保证原子性
//原子类的Integer
private static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++;//不是原子操作
num.getAndIncrement();//AtomicInteger+1;(CAS)
}
public static void main(String[] args) {
for(int i=1;i<=20;i++){
new Thread(()->{
for(int j=0;j<1000;j++){
add();
}
}).start();
}
while(Thread.activeCount()>2){//两个默认线程在执行gc main
Thread.yield();
}
System.out.println(num);
}
}
这些类的底层都是直接和操作系统有关系,在内存中修改值!Unsafe类时一个特殊的存在
3、禁止指令重排
指令重排:
由于内存屏障,volatile可以避免指令重排:
1、保证特定的操作的执行顺序
2、可以保证某些变量的内存可见性(利用volatile的可见性)
深入理解CAS
如果达到期望值,就更新,否则就不更新
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println(atomicInteger.compareAndSet(2020, 2021));//更新成功或失败true
System.out.println(atomicInteger.get());//得到更新后的值2021
System.out.println(atomicInteger.compareAndSet(2020, 2022));//false
System.out.println(atomicInteger.get());//2021
}
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环(自旋锁)
缺点:
1、循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题(狸猫换太子)
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Unsafe类下:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);//获取内存地址中的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//内存操作,提高效率
return var5;
}
原子引用
原子引用解决ABA问题
ABA问题:
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//期望值达到就更新 否则不更新 CAS是CPU并发的原语
//------捣乱的线程
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());//得到更新后的值2021
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());//2021
//---期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 666));
System.out.println(atomicInteger.get());//2021
}
java.util.concurrent.atomic.AtomicReference
可以原子更新的对象引用。 有关原子变量属性的描述
public AtomicReference(V initialValue)用给定的初始值创建一个新的AtomicReference。
带版本号的原子操作!
java.util.concurrent.atomic.AtomicStampedReference
Integer使用了对象缓存机制,默认范围为-128~127推荐使用静态工厂方法valueOf获取对象实例,而不是new 因为valueOf使用缓存,而new一定创建新的对象分配新的内存空间
.【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer var=?在-128 至 127 之间的赋值,Integer 对象是在
IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行
判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,
推荐使用 equals 方法进行判断
解决ABA问题,引入原子引用,对应的思想是乐观锁
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1->"+stamp);
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1,2,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("a2->"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(2,1,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("a3->"+atomicStampedReference.getStamp());
}).start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("b1->"+stamp);
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1,2,stamp,stamp+1);
System.out.println("b2->"+atomicStampedReference.getStamp());
}).start();
}
各种锁
1、公平锁、非公平锁
公平锁:非常公平,不能插队(必须先来后到)
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:非常不公平,能插队(默认都是非公平)
public ReentrantLock() {
sync = new NonfairSync();
}
2、可重入锁
public class LockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone. sms();
},"B").start();
}
}
class Phone{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
lock.lock();//Lock锁必须配对,否则就会死在里面
try{ System.out.println(Thread.currentThread().getName()+"sms");
call();
}finally {
lock.unlock();
lock.unlock();
}
}
public void call(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"call");
}finally {
lock.unlock();
}
}
}
自旋锁(CAS):
public class LockDemo2 {
AtomicReference atomicReference = new AtomicReference();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"->myLock");
while(!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"->myUnLock");
atomicReference.compareAndSet(thread,null);
}
}
import java.util.concurrent.TimeUnit;
public class TestLock {
public static void main(String[] args) throws InterruptedException {
LockDemo2 lockDemo2 = new LockDemo2();
new Thread(()->{
lockDemo2.myLock();
try{
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lockDemo2.myUnLock();
}
},"A").start();
TimeUnit.SECONDS.sleep(3);
new Thread(()->{
lockDemo2.myLock();
try{
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lockDemo2.myUnLock();
}
},"B").start();
}
}
本文地址:https://blog.csdn.net/weixin_44093711/article/details/108608070