JavaSE-09 Thread 多线程(完整版)

JavaSE-09 Thread 多线程

1. 线程简介

1.1 普通方法调用和多线程

在这里插入图片描述

1.2 程序、进程、线程

  • 程序跑起来编程进程,进程里面分为若干个线程 :例如main函数就是主线程(是系统入口,用于执行多个程序),gc垃圾回收机制也是一个线程
  • 多线程是模拟出来的,真正的多线程是指很多cpu,即多核,但是因为cpu执行代码切换的很快,所以有同时执行的感觉
  • 多个线程是由调度器安排调度与操作系统相关的,控制到cpu先后顺序
  • 对同一个资源操作时,会发生资源抢夺的问题,需要加入并发控制

2. 线程创建:com.fenfen.Thread.Demo1

2.1继承Thread类

三步走:
1.自定义线程类继承Thread类
2.重写run()方法,编写线程执行力
3.创建线程对象,调用start()方法启动线程
        public class TestTread1 extends Thread{

            @Override
            public void run() {
                //run 方法线程体
                for (int i = 0; i < 20; i++) {
                    System.out.println("我在通宵肝代码---"+i);

                }
            }
            public static void main(String[] args) {
                //main方法,主线程

                //创建一个线程对象,并调用start方法
                TestTread1 testTread1 = new TestTread1();
                testTread1.start();
                //如果是run方法就是正常的先跑run方法上面的
                testTread1.run();


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

                }
                /*
                输出结果是交替执行的,由cpu调度执行
                 */
            }

2.2 用继承thread实现网图下载的多线程

学完io流后记得补代码

2.3 实现runnable接口

三步走:
1.定义MyRunnable类实现Runnable接口
2.实现run()方法,编写线程执行体
3.创建线程对象,传入目标对象+调用start()方法启动线程
        public class TestThread3 implements Runnable{
            @Override
            public void run() {
                //run 方法线程体
                for (int i = 0; i < 20; i++) {
                    System.out.println("我在通宵肝代码---"+i);

                }
            }
            public static void main(String[] args) {
                //main方法,主线程

                //创建Runnable接口实现类对象,并调用start方法
                TestThread3 testTread3 = new TestThread3();

                //创建线程对象,通过线程对象来开启我们的线程,代理
                Thread thread = new Thread(testTread3);
                thread.start();
                //new Thread(testTread3).start();或者直接一句这个


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

                }
                /*
                1、去看源码发现,本质是因为:Thread也实现了Runnable接口,Runnable就一个run方法在里面
                2、继承是单继承,推荐使用Runnable方法
                 */
            }
        }

2.4 初始并发问题

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

        public class TestThread4 implements Runnable{

            //票数
            private int ticketnums = 10;


            @Override
            public void run() {
                while (true){
                    if (ticketnums<=0){
                        break;
                    }

                    //模拟延迟sleep
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketnums--+"票");
                }
            }

            public static void main(String[] args) {
                TestThread4 ticket = new TestThread4();

                new Thread(ticket,"小明").start();
                new Thread(ticket,"小芬").start();
                new Thread(ticket,"黄牛党").start();
            }
        }

2.5 利用多线程实现龟兔赛跑

思路:
1.fori循环
2.方法用boolean写一个判断是否完成比赛,传递i过去
3.比赛结束跳出循环
4.新建两个线程调用
5.让兔子线程休息,记得try和catch一下
        public class Race implements Runnable{


            //胜利者
            private static String winner;

            @Override
            public void run() {
                for (int i = 0; i <= 100; i++) {
                    //模拟兔子休息sleep
                    if(Thread.currentThread().getName().equals("兔子")&&i%10==0){

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

                    //判断比赛是否结束
                    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) {
                Race race = new Race();

                new Thread(race,"兔子").start();
                new Thread(race,"乌龟").start();
            }
        }

2.6 实现Callable接口

好几步走:
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务
5.提交执行
6.获取结果
7.关闭服务

了解就好,如果以后用到再学,再来补,先鸽一下(狗头)

2.7 静态代理模式:com.fenfen.Thread.Demo2

思路:
1.两个类都改写接口的方法
2.将真实对象通过参数传进去(构造器),代理对象从而代理真实角色
好处:
代理对象可以做很多真实对象做不了的事情,真实对象专注做自己的事情
就是线程的底部原理
  			public class StaticProxy {       
			public static void main(String[] args) {

                You you = new You();

                //这边用lambda表达式表示:Thread代理一个真实的Runnable接口,并且调用了start方法
                new Thread(()-> System.out.println("我爱你")).start();


                //或者精简成new WeddingCompany(new You()).HappyMarry();
                WeddingCompany weddingCompany = new WeddingCompany(new You());
                weddingCompany.HappyMarry();
            }
        }
        interface Marry{
            void HappyMarry();

        }
        //真实角色
        class You implements Marry{
            @Override
            public void HappyMarry() {
                System.out.println("要结婚啦");
            }
        }

        //代理角色,帮助
        class WeddingCompany implements Marry{

            private Marry target;

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

            @Override
            public void HappyMarry() {
                before();
                this.target.HappyMarry();//这就是真实对象
                after();

            }

            private void after() {
                System.out.println("结婚之后,收尾款");
            }

            private void before() {
                System.out.println("结婚之前,布置现场");
            }
        }

2.8 Lambda表达式:com.fenfen.Thread.lamdba

2.8.1 基本内容
  1. Lambda表达式属于函数式编程
  2. 例如:a->System.out.println(“我在学习多线程->”+i);
  3. 函数式接口的定义:任何接口只包含了一个抽象方法,那就是函数式接口,就可以通过lambda表达式来创建该接口的对象
2.8.2 简略代码的方法
  1. 静态内部类
        public class TestLambda2 {

            //利用静态内部类的方式:加上static
            static class Like11 implements ILike1{
                @Override
                public void lambda() {
                    System.out.println("i like lambda1");
                }
            }


            public static void main(String[] args) {


                Like11 like11 = new Like11();
                like11.lambda();
            }

        }
                //1、定义一个函数式接口
                interface ILike1 {
                    void lambda();

                }
  1. 局部内部类
        public class TestLambda3 {


            public static void main(String[] args) {

                class Like3 implements ILike3{
                    @Override
                    public void lambda() {
                        System.out.println("i like lambda3");
                    }
                }
                Like3 like3 = new Like3();
                like3.lambda();
            }

        }
        //1、定义一个函数式接口
        interface ILike3 {
            void lambda();

        }
  1. 匿名内部类:没有类的名称
        public class TestLambda4 {

            public static void main(String[] args) {

                ILike4 like4 = new ILike4(){
                @Override
                public void lambda() {
                    System.out.println("i like lambda4");
                }
            };
                like4.lambda();
            }

        }
        //1、定义一个函数式接口
        interface ILike4 {
            void lambda();

        }
  1. 用lambda简化
        public class TestLambda5 {
            public static void main(String[] args) {
                ILike5 like5= ()->{
                    System.out.println("i like lambda5");
                };

                like5.lambda();
            }

        }
        //1、定义一个函数式接口
        interface ILike5 {
            void lambda();

        }

再写一个

  1. 正常接口代码2
        public class TestLambda6 {

            public static void main(String[] args) {
                Love love = new Love();
                love.love(666);
            }

        }

        interface Ilove{
            void love(int a );
        }

        class Love implements Ilove{
            @Override
            public void love(int a) {
                System.out.println("i love life-->"+a);
            }
        }
  1. 匿名内部类2
        public class TestLambda7 {

            public static void main(String[] args) {

                Ilove1 ilove1 = new Ilove1(){//记得改成接口的类
                    @Override
                    public void love(int a) {
                        System.out.println("i love life-->"+a);
                    }
                };
                ilove1.love(888);
            }

        }

        interface Ilove1{
            void love(int a );
        }

7.用lambda简化2

        public static void main(String[] args) {

            Ilove2 ilove2 = (int a)-> {
                    System.out.println("i love life-->"+a);
                };

            //再简化:①去掉参数类型
            ilove2 = (a)-> {
                System.out.println("i love life-->"+a);
            };

            //再简化:把括号都简化没了
            ilove2 = a->
                System.out.println("i love life-->"+a);

            ilove2.love(888);
        }

    }
    interface Ilove2{
        void love(int a );
    }
  1. 用lambda简化多个参数
        public class TestLambda9 {

            public static void main(String[] args) {

                Ilove3 ilove3 = null;

                ilove3 = (a,b)-> {
                    System.out.println("i love you-->"+a+" "+b);
                };
                ilove3.love(520,1314);
                /*
                多个参数也可以去掉参数类型,要去掉就全部去掉,并且带上括号
                 */

            }

        }
        interface Ilove3{
            void love(int a,int b );
        }

3. 线程状态

3.1线程五个状态

在这里插入图片描述

3.2 线程方法

有优先级,休眠,加入,暂停,停止,是否存活
在这里插入图片描述

3.2.1 线程停止stop:com.fenfen.Thread.ThreadStop

JDK一般不建议使用它自己本身的方法停止线程,一般会建立一个标志位进行终止变量

        public class TestStop implements Runnable {

            //1、设置一个标志位
            private boolean flag = true;

            @Override
            public void run() {
                int i = 0;
                while (flag){
                    System.out. println("run....Thread"+i++);
                }
            }

            //2、设置一个公开的方法停止线程,转换标志位,方便调用的

            public void stop(){
                 this.flag = false;
            }

            public static void main(String[] args) {
                //子线程
                TestStop testStop = new TestStop();

                new Thread(testStop).start();

                for (int i = 0; i < 1000; i++) {
                    System.out.println("main"+i);
                    if (i ==900){
                        //调用stop方法切换标志位,让子线程停止
                        testStop.stop();
                        System.out.println("线程该停止了");
                    }

                }

            }

        }
3.2.2 线程休眠sleep:com.fenfen.Thread.ThreadSleep
  1. sleep作用:模拟网络超时:没有sleep会出现只有一个线程拿了票,有了延迟是为了方法问题的发生性:发生线程安全,即多个线程操作了通过同一个对象
  2. 用sleep模拟当前倒计时
        public class TestSleep2 {

            public static void tenDown() throws InterruptedException{
                int num = 10;

                while (true){
                    Thread.sleep(1000);
                    System.out.println(num--);

                    if (num<=0){
                        break;
                    }
                }
            }

            public static void main(String[] args) {
                try {
                    tenDown(); 
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
  1. 用sleep打印系统当前时间
        public class TestSleep3 {
            public static void main(String[] args) {

                Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间

                while (true){

                    try {
                        System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));//时间格式化工厂
                        startTime = new Date(System.currentTimeMillis());//更新当前时间
                        Thread.sleep(1000);

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
3.2.3 线程礼让yield
  1. 概念:让当前执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态,然后cpu会重新调度,礼让不一定成功,可能调度的还是它
  2. com.fenfen.Thread.ThreadYield
        public class TestYield1 {
            public static void main(String[] args) {
                MyYield myYield = new MyYield();

                new Thread(myYield,"a").start();
                new Thread(myYield,"b").start();
            }


        }


        class MyYield implements Runnable{//alt+回车导入run方法
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+ "线程开始执行");
                Thread.yield();//礼让
                System.out.println(Thread.currentThread().getName()+ "线程停止执行");
            }

        }
3.2.4 线程强制执行join
  1. 概念:join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

  2. com.fenfen.Thread.ThreadJoin

        public class TestJoin1 implements Runnable{
            //main到199的时候,让给thread执行了
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("线程vip已经出现!" + i);
                }

            }

            public static void main(String[] args) throws InterruptedException {
                TestJoin1 testJoin1 = new TestJoin1();
                Thread thread = new Thread(testJoin1);
                thread.start();

                //主线程方法

                for (int i = 0; i < 500; i++) {
                    if (i == 200) {
                        thread.join();//插队
                    }
                    System.out.println("main" + i);

                }
            }
        }
3.2.5 线程状态观测getState
  1. 具体实现如下:
  • 使用thread.getState()观察测试线程的状态:主线程观测子线程状态,当子线程中重写的run方法执行后,子线程就停止了,主线程跳出循环并停止
        public class TestState1 {

            
            public static void main(String[] args) throws InterruptedException {

                //用lambda重写run方法
                Thread thread = new Thread(()->{
                    for (int i = 0; i < 5; i++) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(i);
                    }
                    System.out.println("========");

                });


                //观察状态
                Thread.State state = thread.getState();
                System.out.println(state);


                //观察启动后并再次观察状态
                thread.start();
                state = thread.getState();//可以不用每次都创建一个对象了,节约空间来的
                System.out.println(state);

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

                }

            }

观察测试线程的状态:主线程观测子线程状态,当子线程中重写的run方法执行后,子线程就停止了,主线程跳出循环

  1. 再回顾下线程的五个状态

在这里插入图片描述

3.2.6 线程优先级getPriority().setPriority(int x)
  1. 原理

在Thread类中有几个常量,设置了最小优先值为1,最大优先级是10

在这里插入图片描述

顺便看下setPriority方法

        public final void setPriority(int newPriority) {//此方法需要一个int值
            ThreadGroup g;//线程组
            checkAccess();
            if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
                throw new IllegalArgumentException();//如果线程的优先级超出上面的常量最大值,小于最小值抛一个异常出来
            }
            if((g = getThreadGroup()) != null) {//为空的话,直接默认或者最大都ok
                if (newPriority > g.getMaxPriority()) {
                    newPriority = g.getMaxPriority();
                }
                setPriority0(priority = newPriority);
            }
        }

那就继续顺便咯,在下看getPriority方法,就比较简单们直接返回priority就可以

        public final int getPriority() {
            return priority;
        }
  1. 获取与设置线程的优先级
        public class TestPriority {
            public static void main(String[] args) {
                //获取主线程名字以及优先级:默认是5
                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);//10
                t4.start();

                //t5.setPriority(-1);
                //t5.start();会报错抛出异常

                //t6.setPriority(11);
                //t6.start();会报错抛出异常
            }
        }

        class MyPriority implements Runnable{
            @Override
            public void run() {
                //获取子线程名字以及优先级
                System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
            }
        }

只是意味着获取调度的概率低,并不是优先级高的就一定被最先调用,全看cpu的调度

4 线程同步

4.1基础概念

  1. 并发:同一个对象被多个线程同时操作
  2. 线程同步:就是一种等待机制,多个需要同时访问此对象的线程进行这个对象的等待池形成对列,等待前面线程使用完毕,下一个线程再使用,一般会通过对列加锁的形式保证线程同步,解决安全性
  3. 锁:为了保证线程安全,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,其他线程必须等待,使用后释放锁即可
  4. 用锁后仍然存在以下问题:
  • 一个线程有锁导致其他线程需要此锁的所有线程会被挂起
  • 多线程竞争下,加锁,释放锁会导致比较多的上下文切换以及调度延迟,引起性能问题
  • 优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引发性能倒置问题

4.2 线程不安全的举例:com.fenfen.Thread.Synchronized

4.2.1 火车票超卖的例子:

不安全的买票:票会出现-1的情况

        public class UnsafeBuyTicket {
            public static void main(String[] args) {
                BuyTicket station = new BuyTicket();

                new Thread(station,"你").start();
                new Thread(station,"我").start();
                new Thread(station,"他").start();

            }
        }

        class BuyTicket implements Runnable{

            //票
            private int ticketNums = 10;

            //标志位
            boolean flag = true;//外部停止方式
            @Override
            public void run() {
                //买票
              while(flag){
                  try {
                      buy();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
            }

            private void buy() throws InterruptedException {
                //判断是否邮票
                if(ticketNums<=0){
                    flag = false;
                    return;
                }

                //延迟一下
                Thread.sleep(100);


                System.out.println(Thread.currentThread().getName()+"拿到----->"+ticketNums--);
            }

        }
4.2.2 银行取钱的例子

多人去银行取钱可取到超出余额的钱

        public class UnsafeBank {
            public static void main(String[] args) {
                Account account = new Account(100,"积蓄");

                Drawing you = new Drawing(account,50,"你" );
                Drawing boyfriend = new Drawing(account,100,"boyfriend" );

                you.start();
                boyfriend.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(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //余额=上次余额-取的钱
                account.money = account.money-drawingMoney;
                //你手里的钱
                nowMoney = nowMoney +drawingMoney;

                System.out.println(account.name+"余额为:"+account.money);
                //Thread.currentThread().getName() = this.getName()原因是:继承了Thread的全部方法,因此可以调用this
                System.out.println(this.getName()+"手里钱为:"+nowMoney);
            }
        }
4.2.3 线程不安全的集合
        public class UnsafeList {
            public static void main(String[] args) throws InterruptedException {
                List<String> list = new ArrayList<>();

                for (int i = 0; i < 18000; i++) {//实际输出就17000多
                    new Thread(()->{
                        list.add(Thread.currentThread().getName());//把线程名字添加到集合里面了
                    }).start();
                }

                Thread.sleep(3000);
                System.out.println(list.size());
            }
        }

可能出现两个线程同一瞬间操作了同一个位置,把两个数组添加了同一个位置,覆盖的就是少的元素

4.3 同步方法及同步块

4.3.1 同步方法

火车票超卖修改:将synchronized放在方法前:默认锁的是this类

        public class SafeBuyTicket {
            public static void main(String[] args) {
                BuyTicket1 station = new BuyTicket1();

                new Thread(station,"你").start();
                new Thread(station,"我").start();
                new Thread(station,"他").start();

            }
        }

        class BuyTicket1 implements Runnable{

            //票
            private int ticketNums = 10;

            //标志位
            boolean flag = true;//外部停止方式
            @Override
            public void run() {
                //买票
                while(flag){

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

                    buy();

                    }

            }

            //
            private synchronized void buy(){
                //判断是否邮票
                if(ticketNums<=0){
                    flag = false;
                    return;
                }

                //延迟一下
                //Thread.sleep(1000);//注意这边,sleep不会释放锁,一个线程拿到锁就会一直执行,于是把sleep放在run方法里


                System.out.println(Thread.currentThread().getName()+"拿到----->"+ticketNums--);
            }

        }
4.3.2 同步块
  1. 基本概念:同步块synchronized(Obj){},Obj就是称之为同步监视器,但是推荐使用共享资源作为同步监视器 ,例子中就是锁的是account这个共享资源

  2. 银行取钱修改:

        public class SafeBank {
            public static void main(String[] args) {
                Account1 account1 = new Account1(500,"积蓄");

                Drawing1 you = new Drawing1(account1,50,"你" );
                Drawing1 boyfriend = new Drawing1(account1,100,"boyfriend" );

                you.start();
                boyfriend.start();
            }
        }

        //账户
        class Account1{
            int money;//余额
            String name;//卡名

            public Account1(int money, String name) {
                this.money = money;
                this.name = name;
            }
        }


        //银行:模拟取款
        class Drawing1 extends Thread{

            Account1 account1;//账户
            //要去多少钱
            int drawingMoney;
            //现在手里有多少钱
            int nowMoney;

            //构造器
            public Drawing1(Account1 account1,int drawingMoney,String name){
                super(name);//调用父类的有参构造,是线程的名字
                this.account1 = account1;
                this.drawingMoney = drawingMoney;

            }

            //重写方法
            @Override
            public void run() {
                synchronized (account1) {//锁这个对象,是根据代码中针对哪一些进行了增删改查
                    //判断下有没有钱呢
                    if (account1.money - drawingMoney < 0) {
                        System.out.println(Thread.currentThread().getName() + "钱不够你取了");
                        return;
                    }

                    //延迟下呀,放大线程不安全的发生性
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //余额=上次余额-取的钱
                    account1.money = account1.money - drawingMoney;
                    //你手里的钱
                    nowMoney = nowMoney + drawingMoney;

                    System.out.println(account1.name + "余额为:" + account1.money);
                    //Thread.currentThread().getName() = this.getName()原因是:继承了Thread的全部方法,因此可以调用this
                    System.out.println(this.getName() + "手里钱为:" + nowMoney);
                }
            }
        }

分析:

  • 只锁run方法的时候:只会锁住所在的Drawing类,不会锁住Account类线程

  • 线程“you”执行run方法时发现加锁了,便找到加锁对象(you),发现没有其他线程执行run方法,就持锁执行run方法

  • 线程“boyfriend”执行run方法时发现加锁了,便找到加锁对象,也发现没有其他线程执行run方法,就只锁执行run方法,导致实际上两个线程还是同步运行

  • 真正被两个线程并发访问引起冲突的是账户,因为两个Drawing都是使用同一个account来new的,所以应该锁Account而不是Bank,因此哪个类的属性会发生变化,就锁哪个类的哪个类的对象

  1. 线程不安全的集合的修改
        public class SafeList {
            public static void main(String[] args) throws InterruptedException {
                List<String> list = new ArrayList<>();

                for (int i = 0; i < 18000; i++) {//实际输出就17000多
                    new Thread(()->{
                        synchronized (list) {
                            list.add(Thread.currentThread().getName());//把线程名字添加到集合里面了
                        }
                    }).start();
                }

                Thread.sleep(3000);
                System.out.println(list.size());
            }
        }

把lambda中的list用同步块锁住就可以了

4.3.3 JUC包初始

JUC安全类型的集合:人家写好的类本来就是安全的,我们不需要再去同步了

        public class TestJUC {
            public static void main(String[] args) throws InterruptedException {
                CopyOnWriteArrayList<String > list = new CopyOnWriteArrayList<String>();

                for (int i = 0; i < 10000; i++) {
                    new Thread(()->{
                        list.add(Thread.currentThread().getName());
                            }).start();
                }
                Thread.sleep(3000);

                System.out.println(list.size());
            }
        }

去看源码:

        public CopyOnWriteArrayList() {
            setArray(new Object[0]);
        }
        final void setArray(Object[] a) {
            array = a;
        }
        private transient volatile Object[] array;
        //array用两个关键词修饰了, volatile 是唯一的意思,transient保证是序列化的
		//序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,Java只是以某种形式实现了序列化
4.3.4 死锁
  1. 概念:多个线程都在等待对方释放资源,都停止执行,某一个同步块同时拥有”两个以上对象的锁“,就可能发生”死锁“

  2. 代码实现

        public class DeadLock {
            public static void main(String[] args) {
                Makeup g1 = new Makeup(0,"芬芬");
                Makeup g2 = new Makeup(1,"静静");

                g1.start();
                g2.start();
            }
        }
        //口红
        class LipStick{ }

        //镜子
        class Mirror{ }

        class Makeup extends Thread{

            //static是为了限制只有一份资源
            static LipStick lipStick =  new LipStick();
            static Mirror mirror =  new Mirror();

            //定义选择以及人名
            int choice ;
            String girlName;

            //整一个构造器
            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(2000);

                        synchronized (lipStick){锁中锁,自己没释放,还想去锁拿另一个的资源
                            System.out.println(this.girlName+"获得口红的锁");
                        }
                    }
                }
            }
        }
  1. 化解死锁
        public class DeadLock1 {
            public static void main(String[] args) {
                Makeup1 g1 = new Makeup1(0,"芬芬");
                Makeup1 g2 = new Makeup1(1,"静静");

                g1.start();
                g2.start();
            }
        }
        //口红
        class LipStick1{ }

        //镜子
        class Mirror1{ }

        class Makeup1 extends Thread{

            //static是为了限制只有一份资源
            static LipStick1 lipStick1 =  new LipStick1();
            static Mirror1 mirror1 =  new Mirror1();

            //定义选择以及人名
            int choice ;
            String girlName;

            //整一个构造器
            Makeup1(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 (lipStick1){//把口红锁住
                        System.out.println(this.girlName+"获得口红的锁");
                        Thread.sleep(1000);
                        }
                    synchronized (mirror1){//放到外面来就可以了
                        System.out.println(this.girlName+"获得镜子的锁");
                    }

                }else{

                    synchronized (mirror1){//把镜子锁住
                        System.out.println(this.girlName+"获得镜子的锁");

                        Thread.sleep(2000);
                        }
                    synchronized (lipStick1){//放到外面来就可以了
                        System.out.println(this.girlName+"获得口红的锁");

                    }
                }
            }
        }

4.3.4 Lock锁
  1. 概念:通过显示定义同步锁对象来实现同步,同步锁使用Lock对象,只有一个线程对Lock对象加锁,类似synchronized并发性和内存语义,常用ReentrantLock可重入锁

  2. 看下源码

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();
  1. 具体运用

在这里插入图片描述

        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和finally中
                    try {
                        lock.lock();
                        if(ticketNums>0){
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(ticketNums--);
                        }else {
                            break;
                        }
                    }finally {
                        lock.unlock();//解锁
                    }
                }

            }
        }

5 线程协作:通信问题

5.1 生产者消费者问题

  1. 内容:是一个线程同步问题,生产者和消费者共享一个资源,生产者和消费者之间相互依赖,互为条件

  2. 具体表现:

    • 对于生产者,没有生产产品前,要通知消费者等待,生产了后,依然需要通知消费者消费
    • 对于消费者,消费后,要通知生产者已经结束消费,需要生产新的产品以供消费
    • 在生产者消费者问题中,仅仅有synchronized不够
  3. 线程通信的方法:wait和notify

    • wait()表示线程一直等待,等待到其他线程通知,不像sleep抱着锁睡觉不释放锁,他会释放
    • notify()唤醒一个处于等待状态的线程
  4. 解决方式:

    • 管程法:有一个类似池子的缓冲区,消费者不能直接那生产者数据,生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
    • 信号灯法:做一个判断,判断ok就走,不ok就不走

5.2 管程法

思路:
1、四个类:生产者、消费者、产品、缓冲区
2、缓冲区类中:定义下数组chickens,以及计数器,并写好push放入和pop消费的方法、
3、生产者继承thread后:先初始化下container这个对象,然后拿着这个对象去重写run方法,重写中container.push(new Chicken(i));这句话核心:container有push方法,push方法需要chicken参数,Chicken有id这个属性
4、消费者继承thread后:类似生产者,先初始化下container这个对象,然后拿着这个对象去重写run方法,重写中container.pop().id;这句话核心:container有pop方法,pop方法会返回一个chicken,再去拥有id属性
5、然后去主线程中启动生产者和消费者
        package com.fenfen.Thread.Synchronized.Advanced;

        import com.fenfen.oop.Demo5.C;

        //测试:生产者消费者模型--->利用缓冲区解决:管程法
        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;
            }

            //生产方法:通过重写一个run方法
            @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;
            }

            //消费:也通过重写run方法
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("消费了-->"+container.pop().id+"只鸡");//pop会返回一只鸡

                }
            }
        }

        //产品
        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;
            }


        }

5.3 信号灯法

了解 用flag:boolean flag = true;来进行标记

5.4 线程池

  1. 线程池的API:ExecutorService和Executors

  2. 线程池代码:

        public class TestPool {
            public static void main(String[] args) {
                //1.创建服务,创建线程池
                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());

            }
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值