JAVA多线程

一、线程简介

多任务

多线程

Process与Thread

  • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
  • 而进程则是执行程序的一次执行的过程,它是一个动态的概念。是系统资源分配的单位
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位

概念

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程(回收线程)
  • man()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如CPU的调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

二、线程实现

2.1线程创建

三种创建方式:

1.Thread class 继承Thread类

package com.ThreadCode.Thread;
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TestThread1 extends  Thread {
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码"+i);

        }
    }

    public static void main(String[] args) {
        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start()方法开启线程
        testThread1.start();
        for (int i = 0; i <2000; i++) {
            System.out.println("我在学习多线程"+i);

        }
    }
}

image

由此看出同步执行,由CPU调度

下载图片

package com.ThreadCode.Thread;

import org.apache.commons.io.FileUtils;

import java.io.IOException;
import java.net.URL;
import java.io.File;

public class TestThread2 extends  Thread{
    private  String url;//网络图片地址
    private  String name;//保存的文件名

    public TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件:"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://img.hejiuhua.com/upload/vod/20211216-1/8c5adcfefde22bb2eb88c861e23de33b.jpg","博雪中悍刀行.jpg");
        TestThread2 t2 = new TestThread2("https://img.hejiuhua.com/upload/vod/20210925-1/5b3d9872c0b846fecaf30c6d92368852.jpg","1921.jpg");
        TestThread2 t3 = new TestThread2("https://img.hejiuhua.com/saoheifengbao1920new.jpg","扫黑风暴.jpg");
        t1.start();
        t2.start();
        t3.start();
    }
}
//下载器
class WebDownloader{
    //下载方法
    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异常,downloader方法出现问问题");
        }
    }
}

image

2.Runnable接口 实现Runnable接口

package com.ThreadCode.Runable;

import com.ThreadCode.Thread.TestThread1;

//创建线程方法2:实现Runnable接口,重写Run方法,执行线程需要丢入runnable接口实现类,调用start()方法
public class TestRunnable01  implements  Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码"+i);

        }
    }

    public static void main(String[] args) {

//        TestRunnable01 testRunnable01 = new TestRunnable01();
//        Thread thread = new Thread(testRunnable01);
//        thread.start();
//        简写:
        new Thread(new TestRunnable01()).start();

        for (int i = 0; i <200; i++) {
            System.out.println("我在学习多线程"+i);

        }
    }
}

下载图片

package com.ThreadCode.Runable;



import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class TestRunnabel02 implements Runnable{
    private  String url;//网络图片地址
    private  String name;//保存的文件名

    public TestRunnabel02(String url, String name) {
        this.url = url;
        this.name = name;
    }


    @Override
    public void run() {
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载文件:"+name);
    }

    public static void main(String[] args) {
        TestRunnabel02 t1 = new TestRunnabel02("https://img.hejiuhua.com/upload/vod/20211216-1/8c5adcfefde22bb2eb88c861e23de33b.jpg","博雪中悍刀行.jpg");
        TestRunnabel02 t2 = new TestRunnabel02("https://img.hejiuhua.com/upload/vod/20210925-1/5b3d9872c0b846fecaf30c6d92368852.jpg","1921.jpg");
        TestRunnabel02 t3 = new TestRunnabel02("https://img.hejiuhua.com/saoheifengbao1920new.jpg","扫黑风暴.jpg");
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }


}
class WebDownloader{
    //下载方法
    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异常,downloader方法出现问问题");
        }
    }
}

image

3.Callable接口 实现Callable接口

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPoo(1)
  5. 提交执行:Future result1 = ser.submit(1)
  6. 获取结果:Boolean r1 = result1.get()
  7. 关闭服务:ser.shutdownNow

下载图片

package com.ThreadCode.Callable;

import com.ThreadCode.Runable.TestRunnabel02;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//线程创建方式三,实现callable接口
public class TestCallable implements Callable<Boolean> {
    private  String url;//网络图片地址
    private  String name;//保存的文件名

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public Boolean call() {
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载文件:"+name);
        return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://img.hejiuhua.com/upload/vod/20211216-1/8c5adcfefde22bb2eb88c861e23de33b.jpg","博雪中悍刀行.jpg");
        TestCallable t2 = new TestCallable("https://img.hejiuhua.com/upload/vod/20210925-1/5b3d9872c0b846fecaf30c6d92368852.jpg","1921.jpg");
        TestCallable t3 = new TestCallable("https://img.hejiuhua.com/saoheifengbao1920new.jpg","扫黑风暴.jpg");

        //1. 创建执行服务:ExecutorServier ser = Exectors.newFixedThreadPool(1)
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //2. 提交执行:Future<Boolean> result1 = ser.submit(1)
        Future<Boolean> result1 = ser.submit(t1);
        Future<Boolean> result2 = ser.submit(t2);
        Future<Boolean> result3 = ser.submit(t3);

        //3. 获取结果:Boolean r1 = result1.get()
        Boolean rs1= result1.get();
        Boolean rs2= result2.get();
        Boolean rs3= result3.get();

        //4. 关闭服务:ser.shutdownNow
        ser.shutdown();
    }


}
class WebDownloader{
    //下载方法
    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异常,downloader方法出现问问题");
        }
    }
}

image

小结:

  • 继承Thread类
    • 子类继承Thread类具备多线程能力
    • 启动线程:子类对象
    • 不建议使用:避免OOP单继承局限性
  • 实现Runnable接口
    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start()
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

2.2多个线程操作同一个对象

package com.ThreadCode.Runable;
//多个线程操作同一个对象
//买火车票的例子



//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱

public class TestRunnable03 implements  Runnable{
    //票数
    private int ticketNums =10;

    @Override
    public void run() {
        while (true){

            if(ticketNums<=0){
                break;
            }
//            //模拟延时,要不然太快了
//            try {
//                Thread.sleep(100);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了"+ticketNums--+"票");
        }
    }

    public static void main(String[] args) {
        TestRunnable03 ticke = new TestRunnable03();
        new Thread(ticke,"小明").start();
        new Thread(ticke,"老师").start();
        new Thread(ticke,"黄牛").start();
    }
}

image

2.3案例:龟兔赛跑

  1. 首先来个赛道距离,如何要离终点越来越近
  2. 判断比赛是否结束
  3. 打印出胜利者
  4. 龟兔赛跑开始
  5. 故事中的是乌龟赢,兔子需要睡觉,所以我们来模拟兔子睡觉
  6. 终于,乌龟赢得比赛
package com.ThreadCode.Case;

public class TortoiseAndHare implements Runnable{
    //胜利者
    private  static  String winner;
    @Override
    public void run() {
        //模拟兔子休息
        if(Thread.currentThread().getName().equals("兔子")){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i <= 100; i++) {
            //判断比赛是否结束
            boolean flag = gameOver(i);
            //如果比赛结束了就停止比赛
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
        }
    }

    private  boolean gameOver(int steps){
     //判断是否完成比赛
     if(winner!=null){//已经存在胜利则会
         return  true;
     }
        {
            if(steps>=100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
     return  false;
    }
    public static void main(String[] args) {
        TortoiseAndHare tortoiseAndHare = new TortoiseAndHare();
        new Thread(tortoiseAndHare,"兔子").start();
        new Thread(tortoiseAndHare,"乌龟").start();

    }
}

image

2.4静态代理

package com.ThreadCode.StaticProxy;
//静态代理模式总结:
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色

//好处
//    代理对象可以做很多真实对象做不了的事情
//    真实对象专注做自己的事情

public class TestStaticProxy {
    public static void main(String[] args) {

//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//
//            }
//        }).start();

        You you = new You();//你要结婚

        WeddingCompany weddingCompany = new WeddingCompany(you);//交给代理去做
        weddingCompany.HappyMarru();
    }
}
interface Marry{
    //人间四大喜事
    void HappyMarru();
}

//真实角色
class  You implements  Marry{

    @Override
    public void HappyMarru() {
        System.out.println("结婚");
    }
}
//代理角色
class  WeddingCompany implements Marry{

    private  Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarru() {
        before();
        this.target.HappyMarru();
        after();
    }

    private void before() {
        System.out.println("结婚之前布置现场");
    }
    private void after() {
        System.out.println("结婚之后收尾款");
    }

}

image

2.5Lambda表达式

  • λ希腊字母中排序第十一位的字母,英语名称为Lambda

  • 避免匿名内部类定义过多

  • 其实质属于函数式编程的概念

    new Thread(()->System.out.println("多线程学习....")).start();
    
  • 为什么要是有lambda表达式

    • 避免匿名内部类定义过多
    • 可以让你的代码看起来很简介
    • 去掉一堆没有意义的代码,只留下核心的逻辑
  • 也许你会说,我看了Lambda表达式,不但不觉得简洁,反而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多了,看习惯了,就好了

    理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在

  • 函数式接口的定义

    • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
    • 对于函数式接口,我们可以通lambda表达式来创建该接口的对象
package com.ThreadCode.Lambda;
//推导Lambda表达式
public class TestLambda01 {
    //3.静态内部类
   static class Like2 implements Ilike{

        @Override
        public void lambda() {
            System.out.println("I Like Lambda2");
        }
    }
    public static void main(String[] args) {
        Ilike ilike = new Like1();
        ilike.lambda();
        ilike = new Like2();
        ilike.lambda();

        //4.局部内部类
        class Like3 implements Ilike{

            @Override
            public void lambda() {
                System.out.println("I Like Lambda3");
            }
        }
        ilike = new Like3();
        ilike.lambda();

        //5.匿名内部类,没有类的名称,必须借助接口或者父类
        ilike = new Ilike() {
            @Override
            public void lambda() {
                System.out.println("I Like Lambda4");
            }
        };
        ilike.lambda();
        //6.用lambda简化
        ilike=()-> {
            System.out.println("I Like Lambda5");
        };
        ilike.lambda();
    }
}
//1.定义一个接口
interface Ilike{
    void lambda();
}
//.2实现类
class Like1 implements Ilike{

    @Override
    public void lambda() {
        System.out.println("I Like Lambda1");
    }
}

image

package com.ThreadCode.Lambda;
//总结
//    lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹
//    前提是接口为函数式接口
//    多个参数也可以去掉参数类型,要去掉就都要去掉
public class Testlambda {
    public static void main(String[] args) {
        Ilove ilove = null;
        //原始:
        ilove = new Love();
        ilove.love(1);
        //Lambda表达式
        ilove=(int a)->{ System.out.println("I Love You-->"+a);};
        ilove.love(2);
        //简化1.去掉参数类型
        ilove=(a)->{ System.out.println("I Love You-->"+a);};
        ilove.love(3);
        //简化2.简化括号
        ilove=a->{ System.out.println("I Love You-->"+a);};
        ilove.love(4);
        //简化3.去掉花括号
        ilove=a->System.out.println("I Love You-->"+a);
        ilove.love(5);

    }
}
interface Ilove{
    void love(int a);
}
class  Love implements Ilove{

    @Override
    public void love(int a) {
        System.out.println("I Love You-->"+a);
    }
}

image

三、线程状态

image

线程五大状态:

  • 创建状态 new
  • 就绪状态 start
  • 阻塞状态 sleep......
  • 运行状态 run
  • 死亡状态 dead

image

3.1线程停止

package com.ThreadCode.State.ThreadStop;
//测试stop
//1.建议线程正常停止--->利用次数,不建议死循环
//2.建议使用标志位-->设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class TestThreadStop implements Runnable{
    //1.设置一个标志位
    private  boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("rim___Thread"+i++);
        }
    }
    //2.设置一个公开的方法停止线程,转换标志位
    public  void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestThreadStop testThreadStop = new TestThreadStop();
        new Thread(testThreadStop).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if(i==900)
            {
                //调用stop方法,切换标志位让线程停止
                testThreadStop.stop();
                System.out.println("线程该停止了");}
        }
    }
}

image

image

3.2线程休眠_Sleep

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

模拟网络延时

package com.ThreadCode.State.ThreadSleep;
//模拟网络延时

import com.ThreadCode.Runable.TestRunnable03;

public class TestSleep implements  Runnable{
    //票数
    private int ticketNums =10;

    @Override
    public void run() {
        while (true){

            if(ticketNums<=0){
                break;
            }
            //模拟延时,要不然太快了
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了"+ticketNums--+"票");
        }
    }

    public static void main(String[] args) {
        TestSleep ticke = new TestSleep();
        new Thread(ticke,"小明").start();
        new Thread(ticke,"老师").start();
        new Thread(ticke,"黄牛").start();
    }
}

出现-1

image-20220112220115767

模拟倒计时

package com.ThreadCode.State.ThreadSleep;

public class TestSleep2 {

    public static void main(String[] args) throws InterruptedException {
        tenDown();
    }
    //模拟倒计时
    public static void tenDown() throws InterruptedException {
        int number = 10;
        while (true){
            Thread.sleep(1000);
            System.out.println(number--);
            if(number<=0){
                break;
            }
        }
    }
}

获取当前时间

package com.ThreadCode.State.ThreadSleep;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestSleep2 {

    public static void main(String[] args) throws InterruptedException {
        Date startTime = new Date(System.currentTimeMillis());
        while (true) {
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime = new Date(System.currentTimeMillis());
        }
    }

}

image

3.3线程礼让_yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功,看CPU心情
package com.ThreadCode.State.Yield;
//测试礼让线程
//礼让不一定成功,看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYied myYied = new MyYied();
        new Thread(myYied,"A").start();
        new Thread(myYied,"B").start();
    }

}
class  MyYied implements  Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

image

3.4线程强制执行_join

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
package com.ThreadCode.State.ThreadJoin;
//测试join方法
//想象成插队
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程————>"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //启动线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
        //主线程
        for (int i = 0; i < 500; i++) {
            if(i==200){
                thread.join();
            }
            System.out.println("主线程---->"+i);
        }

    }
}

image

3.5观测线程状态

image

package com.ThreadCode.State;
//观察测试线程的状态
public class TestState {
    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 state = thread.getState();
        System.out.println(state);//NEW

        //观察启动后
        thread.start();
        state =thread.getState();//RUN

        while (state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
            Thread.sleep(1000);
            state = thread.getState();//更新线程状态
            System.out.println(state);//输出状态
        }

    }
}

image

3.6线程优先级

  • Java提供一个线程调度器来监控程序中的启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪一个线程来执行
  • 线程的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级
    • getPriority().setPriority(int xxx)
package com.ThreadCode.Priority;

public class TestPriority {
    public static void main(String[] args) {
        //main默认优先级
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        //先设置优先级,在启动优先级
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();

        //1--10
        t5.setPriority(-1);
        t5.start();
        //1--10
        t6.setPriority(11);
        t6.start();
    }
}
class MyPriority implements  Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

image

3.7守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾挥手等待……
package com.ThreadCode.State.GuardThread;
//测试守护线程
//上帝守护你
public class TestGuardThread {
    public static void main(String[] args) {
        Guard guard = new Guard();
        UserThread  userThread= new UserThread ();

        Thread threadguard = new Thread(guard);
        threadguard.setDaemon(true);//默认flase表示用户线程
        threadguard.start();

        Thread thread = new Thread(userThread);
        thread.start();
    }
}
//守护线程
class Guard implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("这是守护线程-->");
        }
    }
}
//用户线程
class UserThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("用户线程--->"+i);
        }
        System.out.println("用户线程结束");
    }
}

image

四、线程同步

  • 多个线程操作同一个资源
  • 并发:同一个对象被多个线程同时操作
  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可,存在以下问题
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

4.1三个不安全的案例

1.不安全的线程:买票

package com.ThreadCode.Case;

//不安全的买票
//线程不安全
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"小米").start();
        new Thread(buyTicket,"小苹").start();
        new Thread(buyTicket,"小魅").start();
    }
}
class BuyTicket implements Runnable{
    private  int tickeNums = 10;
    boolean flag =true;
    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private  void buy() throws InterruptedException {
        //判断是否有票
        if(tickeNums<=0){
            flag =false;
            return;
        }
        //模拟延时
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到票"+tickeNums--);
    }
}

此时会出现一样的 或者负数

image

2.不安全的线程:取钱

package com.ThreadCode.Case;
//不安全的取钱
//俩个人去银行取钱
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"结婚基金");
        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"女朋友");
        you.start();
        girlFriend.start();
    }
}
//账户
class Account{
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行
class  Drawing extends Thread{
    Account account;
    //取了多少钱
    int drawingMoney;
    //现在手里有多少钱
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    //取钱
    @Override
    public void run() {
        //判断有没有钱
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }
        //模拟延时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money =account.money-drawingMoney;

        nowMoney = nowMoney+drawingMoney;

        System.out.println(account.name+"余额为:"+account.money);
        System.out.println(this.getName()+"取了:"+nowMoney);
    }
}

会出现负数

image

3.不安全集合

package com.ThreadCode.Case;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String >list=new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}

集合数据缺少

image

4.2同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两张用法:synchronized方法和synchronized块

    同步方法:public synchronized void method(int args){}
    
  • synchronized方法控制“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁

    缺陷:若将一个大的方法申明为synchronization将会影响效率
    
  • 方法里面需要修改的内容才需要锁,锁的太多,浪费资源

  • 同步块:

    • 同步块:synchronization(Obj){}
    • Obj称之为同步监视器
      • Obj可以时任何对象,但是推荐使用共享资源作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者时class(反射中讲解)
    • 同步监视器的执行过程
      1. 第一个线程访问,锁定同步监视器,执行其中的代码
      2. 第二个线程范文,发现同步监视器被锁定,无法访问
      3. 第一个线程访问完毕,解锁同步监视器
      4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
synchronized锁的是this,而synchronization(Obj){}不是

1.安全买票

package com.ThreadCode.Case;

public class SafeBuyTicket{
        public static void main(String[] args) {
            Ticket buyTicket = new Ticket();
            new Thread(buyTicket,"小米").start();
            new Thread(buyTicket,"小苹").start();
            new Thread(buyTicket,"小魅").start();
        }
}
class Ticket implements Runnable{
    private  int tickeNums = 10;
    boolean flag =true;
    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //synchronized同步方法,锁的是this
    private synchronized void buy() throws InterruptedException {
        //判断是否有票
        if(tickeNums<=0){
            flag =false;
            return;
        }
        //模拟延时
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到票"+tickeNums--);
    }
}

image

2.安全取钱

package com.ThreadCode.Case;

public class SafeBank {
    public static void main(String[] args) {
        sAccount saccount = new sAccount(100,"结婚基金");
        sDrawing you = new sDrawing(saccount,50,"你");
        sDrawing girlFriend = new sDrawing(saccount,100,"女朋友");
        you.start();
        girlFriend.start();
    }
}
//账户
class sAccount{
    int money;
    String name;

    public sAccount(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行
class  sDrawing extends Thread{
    sAccount saccount;
    //取了多少钱
    int drawingMoney;
    //现在手里有多少钱
    int nowMoney;

    public sDrawing(sAccount saccount, int drawingMoney, String name) {
        super(name);
        this.saccount = saccount;
        this.drawingMoney = drawingMoney;
    }
    //取钱
    //synchronized默认锁的是this
    @Override
    public void run() {
        synchronized (saccount){
            //判断有没有钱
            if (saccount.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }
            //模拟延时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            saccount.money =saccount.money-drawingMoney;

            nowMoney = nowMoney+drawingMoney;

            System.out.println(saccount.name+"余额为:"+saccount.money);
            System.out.println(this.getName()+"取了:"+nowMoney);
        }
    }
}

image

3.安全集合

package com.ThreadCode.Case;

import java.util.ArrayList;
import java.util.List;

public class SafeList {
    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(3000);
        System.out.println(list.size());
    }
}

image

4.JUC安全集合

package com.ThreadCode.Case;

import java.util.concurrent.CopyOnWriteArrayList;
//JUC线程安全集合
public class TestJUC {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                copyOnWriteArrayList.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(copyOnWriteArrayList.size());
    }
}

image

4.3死锁

  • 多个线程各自占有一些资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况。某一个同步块同时拥有“两个以上对象的锁”时。就可能会发生“死锁”的问题
  • 产生死锁的四个必要条件
    1. 互斥条件:一个资源每次只能被一个进程使用
    2. 请求与保持距离:一个进程因请求资源而阻塞时,对以获得的资源保持不放
    3. 不剥夺条件:进程以获得的资源,在使用完之前,不能强行剥夺
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
  • 上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个多个多条件就可以避免死锁发生
package com.ThreadCode.Synchronization;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class Deadlock {
    public static void main(String[] args) {
        Makeup 灰姑凉 = new Makeup(0, "灰姑凉");
        Makeup 白雪公主 = new Makeup(1, "白雪公主");
        灰姑凉.start();
        白雪公主.start();
    }
}
//口红
class Lipstick{

}
//镜子
class Mirror{

}
class Makeup extends  Thread{

    //需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick= new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String girlName;//需要化妆的人

    public Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipstick){//获得口红的锁
                System.out.println(this.girlName+"获得口红");
                Thread.sleep(1000);
                synchronized(mirror){//一秒钟后获得镜子
                    System.out.println(this.girlName+"获得镜子");
                }
            }
        }else{
            synchronized (mirror){//获得口红的锁
                System.out.println(this.girlName+"获得镜子");
                Thread.sleep(1000);
                synchronized(lipstick){//一秒钟后获得镜子
                    System.out.println(this.girlName+"获得口红");
                }
            }
        }
    }
}

出现现象:出现死锁

image

解决:解决互相都需要的锁

package com.ThreadCode.Synchronization;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class Deadlock {
    public static void main(String[] args) {
        Makeup 灰姑凉 = new Makeup(0, "灰姑凉");
        Makeup 白雪公主 = new Makeup(1, "白雪公主");
        灰姑凉.start();
        白雪公主.start();
    }
}
//口红
class Lipstick{

}
//镜子
class Mirror{

}
class Makeup extends  Thread{

    //需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick= new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String girlName;//需要化妆的人

    public Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipstick){//获得口红的锁
                System.out.println(this.girlName+"获得口红");
                Thread.sleep(1000);
                }
            synchronized(mirror){//一秒钟后获得镜子
                System.out.println(this.girlName+"获得镜子");
            }
        }else{
            synchronized (mirror){//获得口红的锁
                System.out.println(this.girlName+"获得镜子");
                Thread.sleep(1000);
                }
            synchronized(lipstick){//一秒钟后获得镜子
                System.out.println(this.girlName+"获得口红");
            }
        }
    }
}

image

4.4Lock(锁)

  • 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能由一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock(可重复锁)类实现了Lock,它拥有与synchronized相同的办法性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁

image
20.cnblogs.com/blog/2459461/202201/2459461-20220112155016907-1610479059.png)

package com.ThreadCode.Synchronization.Lock;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();

    }
}
class TestLock2 implements Runnable{
    int ticketNums = 10;

    //定义lock锁
    private  final ReentrantLock lock = new ReentrantLock();

    @Override

    public void run() {
        while (true){
            try{
                lock.lock();//加锁
                if(ticketNums>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else{
                    break;
                }
            }finally {
                lock.unlock();
            }

        }
    }
}

没有出现紊乱

image

synchronized与Lock的对比

  • Lock是显示锁(手动开启和关闭锁,别忘记关闭所)synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized由代码块和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

五、线程通信问题

image

image

image

image

image

5.1管程法:难点

package com.ThreadCode.communication;


//测试:生产者消费者模型->利用缓冲区解决:管程法

//生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}
//生产者
class Productor extends Thread{

    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }
    //生产

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }
    }
}
//消费者
class Consumer extends Thread{
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了---->"+container.pop().id+"只鸡");
        }
    }
}
//产品
class Chicken{
    int id;//产品编号

    public Chicken(int id) {
        this.id = id;
    }
}
//缓冲区
class SynContainer{
    //需要一个容器大小
    Chicken[] chickens = new Chicken[10];

    //容器计数器
    int count = 0;

    //生产者放入产品
    public  synchronized void push(Chicken chicken){
        // 如果容器满了,就需要等待消费消费
        if(count== chickens.length){
            //通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

        //如果没有满,我们就需要丢入产品
        chickens[count]=chicken;
        count++;

        //可以通知消费者消费了
        this.notifyAll();
    }

    //消费者消费产品
    public  synchronized  Chicken pop(){
        //判断能否消费
        if(count==0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken = chickens[count];

        //吃完了,通知生产者生产
        this.notifyAll();
        return chicken;
    }

}

image

5.2信号灯法:难点

package com.ThreadCode.communication;
//测试生产者消费问题2:信号灯法,标志位解决
public class TestPC2 {
    public static void main(String[] args) {
       TV tv = new TV();
       new Player(tv).start();
       new Watcher(tv).start();
    }
}
//生产者-->演员
class  Player extends Thread{
    TV tv;
    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i%2==0){
                this.tv.play("火星情报局");
            }else{
                this.tv.play("广告");
            }
        }
    }
}
//消费者-->观众
class  Watcher extends Thread{
    TV tv;
    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
           tv.watch();
        }
    }
}
//产品  -->节目
class TV{
    //演员表演,观众等待
    //观众观看,演员等待
    String voice;//表演的节目
    boolean flag = true;

    //表演
    public  synchronized void play(String voice) {
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("演员表演了"+voice);
        //通知观众观看

        this.notifyAll();//通知唤醒
        this.voice = voice;
        this.flag =!this.flag;
    }
    //观看
    public  synchronized  void watch(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:"+voice);
        //通知演员表演
        this.notifyAll();//通知唤醒
        this.flag =!this.flag;
    }

}

image

六、使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完返回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的交通工具
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理(……)
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

image

2.测试线程池:Runnable

package com.ThreadCode.Case;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池Runnable
public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPoo的参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2.关闭链接
        service.shutdown();
    }

}
class  MyThread implements  Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

image

2.测试线程池:callable

package com.ThreadCode.Callable;

import com.ThreadCode.Runable.TestRunnabel02;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//线程创建方式三,实现callable接口
public class TestCallable implements Callable<Boolean> {
    private  String url;//网络图片地址
    private  String name;//保存的文件名

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public Boolean call() {
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载文件:"+name);
        return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://img.hejiuhua.com/upload/vod/20211216-1/8c5adcfefde22bb2eb88c861e23de33b.jpg","博雪中悍刀行.jpg");
        TestCallable t2 = new TestCallable("https://img.hejiuhua.com/upload/vod/20210925-1/5b3d9872c0b846fecaf30c6d92368852.jpg","1921.jpg");
        TestCallable t3 = new TestCallable("https://img.hejiuhua.com/saoheifengbao1920new.jpg","扫黑风暴.jpg");

        //1. 创建执行服务:ExecutorServier ser = Exectors.newFixedThreadPool(1)
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //2. 提交执行:Future<Boolean> result1 = ser.submit(1)
        Future<Boolean> result1 = ser.submit(t1);
        Future<Boolean> result2 = ser.submit(t2);
        Future<Boolean> result3 = ser.submit(t3);

        //3. 获取结果:Boolean r1 = result1.get()
        Boolean rs1= result1.get();
        Boolean rs2= result2.get();
        Boolean rs3= result3.get();

        //4. 关闭服务:ser.shutdownNow
        ser.shutdown();
    }


}
class WebDownloader{
    //下载方法
    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异常,downloader方法出现问问题");
        }
    }
}

image

七、高级主题

7.1 任务定时调度

通过Timer和TimerTask,我们可以实现定时启动某个线程。

  • java.util.Timer:类似闹钟的功能,本身实现的就是一个线程
  • java.util.TimerTask:一个抽象类,该类实现了Runnable接口,所以该类具备了多线程的能力
/**
 * 任务调度:借助Timer 和 TimerTask 实现
 */
public class TimerTask {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // 只执行一次
        // timer.schedule(new MyTask(), 6000L);
        // 多次执行,五秒后执行,每隔两秒打印一次
        Calendar c = new GregorianCalendar(2019, 3, 7, 16, 11, 00);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(c.getTime()));
        timer.schedule(new MyTask(), c.getTime(), 2000L);
    }
}
// 任务类 (多线程)
class MyTask extends java.util.TimerTask {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("放空大脑,休息一下。 :-)");
        }
        System.out.println("本次结束了。。。。");
    }
}

7.2 任务调度框架(Quartz)

Quartz介绍

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。

Spring框架已经集成了 Quartz

Quartz分为组成部分:

  • Scheduler:调度器,控制所有调度
  • Trigger:触发条件,采用DSL模式
  • JobDetail:需要处理的Job
  • Job:执行逻辑

举例例子用到了Quartz相关jar包,下载地址:http://www.quartz-scheduler.org/downloads

/**
 * 任务
 */
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        System.out.println("--------开始--------");
        System.out.println("Hello World! - " + new Date());
        System.out.println("--------结束--------");
    }

}
---------------------------------------------------------------------------------------
/**
 * 任务处理
 */
public class QuartzTest {

  public static void main(String[] args) throws Exception {
    // 1、创建Schedule工厂
    SchedulerFactory sf = new StdSchedulerFactory();
    // 2、从工厂获取调度器
    Scheduler sched = sf.getScheduler();
    // 3、创建JobDetail withIdentity() 方法参数 放入唯一标识 job1、group1
    JobDetail job = JobBuilder.newJob(HelloJob.class)
            .withIdentity("job1", "group1").build();
    // 获取下一秒时间 下一秒开始执行
    Date runTime = DateBuilder.evenSecondDateAfterNow();
    // 4、触发器 withIdentity() 方法参数 放入唯一标识 trigger1、group1
    // 执行一次
    // Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
    // 间隔执行  间隔五秒,重复三次
    SimpleScheduleBuilder simpleSchedule = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3);
    SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(runTime).withSchedule(simpleSchedule).build();
    //5、 注册任务和触发条件
    sched.scheduleJob(job, trigger);
    // 启动
    sched.start();
    // 等待20秒
    try {
      Thread.sleep(20L * 1000L);
    } catch (Exception e) {
      e.printStackTrace();
    }
    // 20秒后停止任务
    sched.shutdown(true);
  }
}

7.3 数据依赖

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖。

数据依赖分为以下三种类型:

名称 代码示例 说明
写后读 a = 1; b = a; 写一个变量之后,再读这个变量
写后写 a = 1; a = 2; 写一个变量之后,再写这个变量
读后写 a = b; b = 1; 读一个变量之后,再写这个变量

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。所以,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

执行步骤:
1、获取指令
2、从寄存器中存储值
3、操作
4、写回

7.4volatile

volatile保证了线程间变量的可见性,简单地说就是当线程A对变量X进行了修改,在线程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:

  • 线程对变量进行修改之后,要立刻回写到主内存
  • 线程对变量读取的时候,要从主内存中读,而不是缓存
    各线程的工作内存间彼此独立、互补可见,在线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量(非线程内构造的对象)的副本,即为提高执行效率

volatile是不错的机制,但是volatile不能保证原子性

/**
 * volatile 用于保证数据的同步,也就是可见性
 */
public class VolatileTest {
    private volatile static int num = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            // 此处没有在多线程情况下同步 num ,所以即使num不为0时还是会一直运行
            // 解决办法,加上 volatile 关键字修饰 num 变量
            for (; num == 0; ) {
                // 此处不写代码
            }
        }).start();
        Thread.sleep(1000L);
        num = 1;
    }
}

7.5ThreadLocal

代表每个线程本地存储区域。

  • 在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程
  • ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到线程安全的目的。说白了,ThreadLocal就是在多线程环境下保证成员变量的安全,常用方法有:
    • get
    • set
    • initialValue

每个线程自身的存储本地、局部区域

/**
 * ThreadLocal:每个线程自身的存储本地、局部区域
 */
public class ThreadLocalTest01 {
    // 初始化
    // private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    // 更改初始值
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) {
        threadLocal.set(99);
        System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());

        new Thread(new MyRun()).start();
        
        new Thread(new MyRun()).start();
    }

    public static class MyRun implements Runnable {
        @Override
        public void run() {
            threadLocal.set((int) (Math.random() * 99));
            System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
        }
    }
}

每个线程自身的数据,更改后不会影响其它线程

/**
 * 利用ThreadLocal实现发糖果小案例
 * 每个线程自身的数据,更改后不会影响其它线程
 */
public class ThreadLocalTest02 {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new MyRun()).start();
        }
    }
    public static class MyRun implements Runnable {
        @Override
        public void run() {
            Integer left = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + "得到了-->" + left);
            threadLocal.set(left - 1);
            System.out.println(Thread.currentThread().getName() + "还剩下->" + threadLocal.get());
        }
    }
}

分析ThreadLocal上下文(环境)

/**
 * 分析ThreadLocal上下文(环境)
 */
public class ThreadLocalTest03 {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
    public static void main(String[] args) {
        new Thread(new MyRun()).start();
    }
    public static class MyRun implements Runnable {
        public MyRun() {
            // 这里的 Thread.currentThread().getName() 实际上时 main 方法里的
            // 在这里修改 threadLLocal的值,只对main线程有影响
            // 构造器 哪里调用,就属于哪里
            System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "还剩下->" + threadLocal.get());
        }
    }
}

7.6 可重入锁

锁作为并发共享数据保证一致性的工具,大多数内置锁都是可重入的,也就是说,如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器将会递减,当计数值等于0时,锁释放。如果没有可重入锁的支持,在第二次企图获取锁时将会进入死锁状态。

不可重入锁

/**
 * 不可重入锁:锁不可以延续使用
 */
public class NotReentrantLock {

    private Lock lock = new Lock();

    public void a() {
        lock.lock();
        b();
        lock.unLock();
    }

    // 不可重入锁
    public void b() {
        lock.lock();

        lock.unLock();
    }

    public static void main(String[] args) {
        NotReentrantLock lock = new NotReentrantLock();
        lock.a();
        lock.b();
    }

}

class Lock {
    // 是否占用
    private boolean isLockd = false;

    // 使用锁
    public synchronized void lock() {
        for (; isLockd; ) {
            try {
                wait(); // 等待,线程进入阻塞状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        isLockd = true;
    }
    // 释放锁
    public synchronized void unLock() {
        isLockd = false;
        notify();   // 唤醒等待线程
    }

}

可重入锁(JUC包下JDK提供了ReentrantLock类实现重入锁)

/**
 * 可重入锁:锁可以延续使用
 */
public class ReentrantLock {

    // private java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock();
    private ReLock lock = new ReLock();

    public void a() {
        lock.lock();
        System.out.println(lock.getHoldCount());
        b();
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }

    // 不可重入锁
    public void b() {
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        lock.a();

        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(lock.lock.getHoldCount());
    }

}

class ReLock {
    // 是否占用
    private boolean isLockd = false;
    // 存储线程
    private Thread lockedBy = null;
    // 计数器
    private int holdCount = 0;

    // 使用锁
    public synchronized void lock() {
        Thread thread = Thread.currentThread();
        for (; (this.isLockd && (this.lockedBy != thread)); ) {
            try {
                wait(); // 等待,线程进入阻塞状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.isLockd = true;
        this.lockedBy = thread;
        this.holdCount ++;
    }
    // 释放锁
    public synchronized void unlock() {
        if (this.lockedBy == Thread.currentThread()) {
            this.holdCount--;
            // 等于0 标识没有使用了
            if (this.holdCount == 0) {
                this.isLockd = false;
                notify();   // 唤醒等待线程
                this.lockedBy = null;
            }
        }
    }
    public int getHoldCount() {
        return holdCount;
    }
}

7.7 CAS 原子操作

锁分为两类:

  • 悲观锁:synchronized时独占锁即悲观锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
  • 乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

Compare and Swap 比较并交换:

  • 乐观锁的表现
  • 有三个值:一个当前内存值V、旧的预期值A、将更新的值B。先获取到内存当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修改的值B并返回true,否则什么都不做,并返回false。
  • CAS是一组原子操作,不会被外部打断。
  • 属于硬件级别的操作(利用CPUCAS指令,同时借助JNI来完成的非阻塞算法),效率比加锁操作高。
  • ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其它线程修改过了吗?如果在这期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。
/**
 * CAS:比较并交换
 */
public class CAS {

    // 库存
    private static AtomicInteger stock = new AtomicInteger(5);

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    // 模拟网络延时
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int result = stock.decrementAndGet();
                String name = Thread.currentThread().getName();
                if (result < 1) {
                    System.out.println(name + "--->抢完了。。。");
                    return;
                }
                System.out.println(name + "抢了一个商品--->还剩" + result + "商品");
            }).start();
        }
    }
}

八、总结

package com.ThreadCode;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Sunmarize {
    public static void main(String[] args) {

        //THread类
        new MyThread1().start();

        //THread类
        new Thread(new MyThread2()).start();
        
        //THread类
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
        new Thread(futureTask).start();
        try {
            Integer integer = futureTask.get();
            System.out.println("Callable返回值"+integer);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
//1.继承THread类
class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread1");
    }
}

//2.实现THread类
class  MyThread2 implements Runnable{
    @Override
    public void run() {
        System.out.println("MyThread2");
    }
}
//3.实现THread类
class  MyThread3 implements Callable{

    @Override
    public Integer call() throws Exception {
        System.out.println("MyThread3");
        return  100;
    }
}

image

posted @ 2022-01-12 22:32  项sir  阅读(101)  评论(0)    收藏  举报
标题