欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

多线程和JUC

程序员文章站 2022-03-26 17:13:24
进程进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有他自己的内存空间和系统资源多进程意义在于计算机可以执行多个任务,提高cpu使用率我们在一边玩游戏,一边听音乐的时候,是cpu在做着程序间的高效切换让我们觉得是同时进行的线程线程是依赖于进程而存在的,在一个进程内又可以执行多个任务,而这每个任务我就可以看出是一个线程线程:是程序的执行单元,执行路径。是程序使用cpu的最基本单位。单线程:程序只有一条执行路径多线程:程序有多条执行路径多线程意义在于提高应用程序的使用率...

进程

进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有他自己的内存空间和系统资源
多进程意义在于计算机可以执行多个任务,提高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