多线程入门

1.线程概述

1.什么是进程

是程序的一次执行过程,或是正在运行的一个程序,是一个动态过程,有他自身的产生,纯在和消亡的过程

2.什么是线程

进程可进一步细化为线程,是一个程序内部的一条执行路径

3.什么是程序

是为完成特定的任务,用某种语言编写的一组指令的集合,即使一段静态的代码,静态对象

4.并行和并发

并行:多个CPU同时执行多个任务,比如多个人同时做不同的事(一遍吃饭一遍完手机)

并发:同时执行多个任务,比如秒杀平台,多个人做一件事

2.线程入门

1.多线程创建方式1
package com.xjggb.thread;

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

        Threadl threadl1 = new Threadl("A");
        Threadl threadl2 = new Threadl("B");
        threadl1.start();
        threadl2.start();


    }
}

/*
 * 方式一
 * 步骤
 * 继承Thread类
 * 重写run方法
 * 创建对象调用start方法
 * */
class Threadl extends Thread {

    private String name;

    public Threadl(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("运行" + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }
}

运行结果

运行0
运行0
运行1
运行2
运行3
运行4
运行1
运行2
运行3
运行4

小结

开发步骤

  1. 继承Thread类
  2. 重写run方法
  3. 创建子类对象调用start

注意:start()方法调用后并不是立即执行多线程代码,而是使得该线程变为可运行状态(Runnable),什么时候运行由操作系统决定

创建线程下载图片

添加依赖

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>

    </dependencies>

代码如下

package com.xjggb.entity;

import org.apache.commons.io.FileUtils;

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

//图片下载
public 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异常");
        }

    }

}

//创建线程
class TestDemo extends Thread{

    private  String url; //网络图片地址
    private  String name; // 保存的文件名

    public TestDemo(String url ,String name){

        this.name=name;
        this.url=url;
    }

    @Override
    public void run() {

        WebDownLoader s = new WebDownLoader();
        s.downLoader(url,name);
        System.out.println("已经下载的文件名称为"+name+"图片" );
    }
}

class Test{
    public static void main(String[] args) {
        TestDemo testDemo1 = new TestDemo("https://img-blog.csdnimg.cn/20210210194321356.png","我的博客图片01.png");
        TestDemo testDemo2 = new TestDemo("https://img-blog.csdnimg.cn/20210228094648502.png","我的博客图片02.png");
        TestDemo testDemo3 = new TestDemo("https://img-blog.csdnimg.cn/20210303104522231.png","我的博客图片03.png");
        //调用start()方法
        testDemo1.start();
        testDemo2.start();
        testDemo3.start();

    }
}



2.多线程创建方式2

开发步骤

  1. 创建类实现Runnable接口重写run方法
  2. 创建接口子类,创建线程对象子类放入线程对象的构造方法中
  3. 调用start方法
package com.xjggb.thread;


public class TestRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我在秀代码" + i);
        }
    }

    public static void main(String[] args) {

        //创建线程接口实现对象
        TestRunnable testRunnable = new TestRunnable();
        //床架线程对象,通过线程对象开启线程

        Thread thread = new Thread(testRunnable);
        //掉用start方法
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("我是主线程" + i);
        }


    }
}





小结

  • 实现Runnable接口
  • 创建子线程传入到线程对象的构造方法中
  • 调用start()方法
  • 推荐使用,避免单继承灵活方便同一对象被多线程调用
3.多线程创建方式3(了解)

现实步骤

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


import org.apache.commons.io.FileUtils;

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

public class Test03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //创建线程
        TestCallable testCallable1 = new TestCallable("https://img-blog.csdnimg.cn/20210210194321356.png", "我的博客图片01.png");
        TestCallable testCallable2 = new TestCallable("https://img-blog.csdnimg.cn/20210228094648502.png", "我的博客图片02.png");
        TestCallable testCallable3 = new TestCallable("https://img-blog.csdnimg.cn/20210303104522231.png", "我的博客图片03.png");

        //创建执行任务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        Future<Boolean> submit1 = ser.submit(testCallable1);
        Future<Boolean> submit2 = ser.submit(testCallable2);
        Future<Boolean> submit3 = ser.submit(testCallable3);

        //获取结果
        Boolean aBoolean1 = submit1.get();
        Boolean aBoolean2 = submit2.get();
        Boolean aBoolean3 = submit3.get();

        //关闭服务
        ser.shutdown();

    }

}

class TestCallable implements Callable<Boolean> {
    private String url; //网络图片地址
    private String name; // 保存的文件名

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


    @Override
    public Boolean call() throws Exception {

        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoader(url, name);
        System.out.println("下载的文件名称为" + name + "的图片");
        return true;
    }
}


//下载器
//图片下载
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异常");
        }

    }

}

3.线程状态

  1. 新建状态(New):新新建一个线程对象
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的starta()方法,该状态的线程位于可运行线程池中,变得可运行等待获取CPU的使用权
  3. 运行状态(Running)就绪状态的线程获取了CPU,执行程序代码
  4. 阻塞状态(Blocked):阻塞状态线程因为某种原因放弃CPU使用权, 暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
    • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

小结:

线程状态

  1. 创建
  2. 就绪
  3. 运行
  4. 阻塞
  5. 死亡

4.线程API

Thread.currentThread().getName() //获取当前线程的名字

1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活

1.线程停止
  • 测试Stop
  • 1.建议线程正常停止—>利用次数,不建议死循环.
  • 2.建议使用标志位---->设置一个标志位
  • 3.不要使用stop和destroy 等过时或者jdk不建议使用的方法.
package com.xjggb.thread;


//建议线程正常停止:利用此时,不建议死循环
//建议使用标记位
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 < 30; i++) {
            System.out.println("main " +i);
            if( i == 20){
                //调用stop方法切换线程标志位,让线程停止
                testStop.stop();
                System.out.println("线程已停止");
            }
        }
    }
}





2.线程睡眠

每个对象都有一个锁,sleep不会释放锁

sleep(时间)指定当前线程阻塞的毫秒数

sleep可以模拟网络延迟,倒计时等

每个对象都有一个锁,seep不会释放锁

模拟倒计时

package com.xjggb.thread;

public class Test04 {
    public static void main(String[] args) {
        try {
            show(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
//    模拟倒计时
    public static void show(int add) throws InterruptedException {

        while (true) {

            Thread.sleep(1000);

            System.out.println("add = " + add--);
            if (add < 0) {
                break; //结束循环
            }

        }

    }
}

打印当前系统时间

package com.xjggb.thread;

public class Test04 {
    public static void main(String[] args) {
        try {
            show(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 //打印当前系统时间
    public static void show() throws InterruptedException {
        Date date = new Date(System.currentTimeMillis());  //获取系统当前的时间

        int i=10;

        while (true){
          Thread.sleep(1000);
            String format = new SimpleDateFormat("HH:mm:ss").format(date);
            System.out.println("format = " + format);

            //获取当前最新的时间
            date = new Date(System.currentTimeMillis());
            --i;
            if (i<0){
                System.out.println(" = " +i);
                break;
            }

        }



    }
}

3.线程礼让
  1. 礼让线程,让当前正在执行的线程暂停,单不堵塞
  2. 将线程从运行状态转为就绪状态
  3. 让CPU重新调度,礼让不一定成功,看CPU

代码如下:

package com.xjggb.thread;

public class ThreadYield {
    public static void main(String[] args) {
        //创建线程子对象
        TestYield testYield = new TestYield();
        //创建线程对象
        new Thread(testYield,"线程A").start();
        new Thread(testYield,"线程B").start();

    }

}


class TestYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开启");
        //线程礼让
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "线程开启");

        
        /* 礼让成功  礼让不成功就多执行两次
        *   线程A线程开启
            线程B线程开启
            线程A线程开启
            线程B线程开启
        * */

    }
}

4.插入线程

join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执

代码如下

package com.xjggb.thread;

public class ThreadJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("run+我是VIP都滚开" + i);
        }

    }

    public static void main(String[] args) {

        //启动线程
        ThreadJoin threadJoin = new ThreadJoin();
        Thread thread = new Thread(threadJoin);


        //主线程

        for (int i = 0; i < 500; i++) {
            System.out.println("我是Main方法" + i);

            if (i == 300) {
                //启动VIP线程
                thread.start();
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
    /*
    *  礼让显示
    *   我是Main方法297
        我是Main方法298
        我是Main方法299
        我是Main方法300
        run+我是VIP都滚开0
        run+我是VIP都滚开1
        run+我是VIP都滚开2
        run+我是VIP都滚开3
        run+我是VIP都滚开4
        run+我是VIP都滚开5
        run+我是VIP都滚开6
        run+我是VIP都滚开7
    * */

        }


    }

}

5.线程检测状态

线程状态。线程可以处于一下状态

  • NEW

尚未启动的线程的线程状态

代码如下

package com.xjggb.thread;

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

        //创建线程
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

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


        //启动后观察状态
        thread.start();

        state1 = thread.getState();
        System.out.println("state2 = " + state1);


        //只要线程不终止,就会一直输出状态
        while (state1 != Thread.State.TERMINATED) {
            try {
                Thread.sleep(200);
                //更新线程状态
                state1 = thread.getState();
                System.out.println("state1 = " + state1);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        /*
        *   state1 = NEW
            state2 = RUNNABLE
            state1 = RUNNABLE
            state1 = RUNNABLE
            state1 = TERMINATED
        * */

    }
}

6.线程优先级

线程优先级用数字表示,范围从1~10

  • Thread.MIN_PRIORITY = 1
  • _PRIORITY = 5
  • Thread.MAX_PRIORITY = 10

注意先设计优先级在启动

代码如下:

package com.xjggb.thread;

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

        System.out.println(" = " + Thread.currentThread().getName());

        Mypriority mypriority = new Mypriority();
        Thread thread1 = new Thread(mypriority);
        Thread thread2 = new Thread(mypriority);
        Thread thread3 = new Thread(mypriority);
        Thread thread4 = new Thread(mypriority);
        Thread thread5 = new Thread(mypriority);
        Thread thread6 = new Thread(mypriority);

        thread1.start();

        //设置优先级
        thread2.setPriority(3);
        thread2.start();

        thread3.setPriority(5);
        thread3.start();

        thread4.setPriority(7);
        thread4.start();

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

        thread6.setPriority(Thread.NORM_PRIORITY);
        thread6.start();


         /*  运行结果
            *  = main
            Thread-3--->7
            Thread-4--->10
            Thread-0--->5
            Thread-2--->5
            Thread-5--->5
            Thread-1--->3
            * */

    }
}

class Mypriority implements Runnable {

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


7.守护线程
  • 线程分为用户线程和守护线程
  • 虚拟机必须确保线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如后台记录操作日志,监控内存,垃圾回收等
package com.xjggb.thread;

public class Test06 {

    public static void main(String[] args) {

        //创建上帝
        Gog gog = new Gog();
        //创建你
        You you = new You();
        //创建线程对象
        Thread thread = new Thread(gog); //使用上帝作为守护线程
        //线程守护默认是false表示用户线程,正常的线程就是用户线程
        thread.setDaemon(true);
        thread.start();

        //you启动
        new Thread(you).start();

    }
}
class  Gog implements Runnable{
    @Override
    public void run() {

        while (true){
            System.out.println("上帝守护你");
        }
    }
}


class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println("我还开开心心的活着" + i+"岁");
        }
        System.out.println("任务结束了");

    }
}
   /*
     * 当主线程退出时,JVM会随之退出运行,守护线程同时也会被回收,即使你里面是个死循环也不碍事
     *
    *   上帝守护你
        上帝守护你
        我还开开心心的活着0岁
        我还开开心心的活着1岁
        我还开开心心的活着2岁
        我还开开心心的活着3岁
        我还开开心心的活着4岁
        我还开开心心的活着5岁
        我还开开心心的活着6岁
        我还开开心心的活着7岁
        我还开开心心的活着8岁
        我还开开心心的活着9岁
        任务结束了
        上帝守护你
        上帝守护你
    * */

小结:

如果 JVM 中没有一个正在运行的非守护线程,这个时候,JVM 会退出。换句话说,守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点

5.什么是线程安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

1.没有线程同步
package com.xjggb.security;

public class Test02 {

    public static void main(String[] args) {

        //创建子线程实现列
        WindowRunnable windowRunnable = new WindowRunnable(100);

        //创建线程对象
        Thread thread1 = new Thread(windowRunnable);
        Thread thread2 = new Thread(windowRunnable);
        Thread thread3 = new Thread(windowRunnable);

        thread1.start();
        thread2.start();
        thread3.start();
        
            /*
        * Thread-0售出票号100
          Thread-2售出票号100
          Thread-2售出票号99
          Thread-1售出票号100
        * */
        

    }

}

class WindowRunnable implements  Runnable{

    //定义票数
    private  int ticket;

    public WindowRunnable(int ticket) {
        this.ticket = ticket;
    }

    @Override
    public void run() {

        while (true){
          if (ticket>0){
              System.out.println(Thread.currentThread().getName() +"售出票号"+ ticket);
              ticket--;
          }else {
              break;
          }
        }

    }
    
}

由于线程被CPU调度的随机性,多次运行结果不一致,但基本一致可以看出,多个窗口在迸发进行销售,而且有

错票,重要 ,漏票问题

2.有线程同步
同步代码块

synchronized(同步监视器){

需要被同步的代码

}

  1. 操作共享数据的代码,需要被同步的代码
  2. 共享数据,多个线程共同操作的变量,比如:ticket九四共享数据
  3. 同步监视器,俗称锁,任何一个类对象,都可以充当锁
package com.xjggb.security;

public class Test02 {

    public static void main(String[] args) {

        //创建子线程实现列
        WindowRunnable windowRunnable = new WindowRunnable(100);

        //创建线程对象
        Thread thread1 = new Thread(windowRunnable);
        Thread thread2 = new Thread(windowRunnable);
        Thread thread3 = new Thread(windowRunnable);

        thread1.start();
        thread2.start();
        thread3.start();

        /*
        * Thread-0售出票号100
          Thread-2售出票号100
          Thread-2售出票号99
          Thread-1售出票号100
        * */

    }

}

class WindowRunnable implements  Runnable{

    //定义票数
    private  int ticket;

    public WindowRunnable(int ticket) {
        this.ticket = ticket;
    }

    @Override
    public void run() {

        synchronized (WindowRunnable.class){  //同步代码块
            while (true){
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName() +"售出票号"+ ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }


    }
}
同步方法
package com.xjggb.security;

public class Test04 {
    public static void main(String[] args) {
        //创建线程
        Window2 window2 = new Window2();

        //创建线程
        Thread thread1 = new Thread(window2,"线程1");
        Thread thread2 = new Thread(window2,"线程2");
        Thread thread3 = new Thread(window2,"线程3");

        //启动线程
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

class Window2 implements Runnable{
    //创建票数
    private  int ticket=100;
    @Override
    public void run() {
        while (true){

            show();
        }
    }


    //同步方法
    private synchronized   void show(){
        if (ticket>0){

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

            System.out.println(Thread.currentThread().getName()+":"+ticket);
            ticket--;

        }

    }


}

7.死锁

1.什么是死锁
  1. 不同的线程分别抢占用对方需要的同步资源不放弃,都在等待对方放弃,自己需要的同步志愿,就形成了线程死锁
  2. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

代码如下:

package com.xjggb.security;

public class Test05 {

    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        //创建线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append("1");

                    //睡眠两秒
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //在嵌套一个线程安全
                    synchronized (s2) {


                        s1.append("b");
                        s2.append("2");

                        System.out.println("s2 = " + s2);
                        System.out.println("s1 = " + s1);
                    }
                }

            }
        }).start();

        //在创建一个线程

        new Thread(() -> {
            synchronized (s2) {
                s1.append("o");
                s2.append("7");
                //睡眠两秒
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //在嵌套一个线程安全
                synchronized (s1) {


                    s1.append("p");
                    s2.append("8");


                    System.out.println("s2 = " + s2);
                    System.out.println("s1 = " + s1);
                }
            }

        }).start();


    }

}

小结:

程序不会结束,也无法执行下去

2.解决方法
  1. 专门的算法,原则
  2. 尽量减少同步资源的定义
  3. 尽量避免嵌套同步

8.Lock(锁)

  • java提供了更强大的线程同步机制–通过显示定义同步代锁对象来实现同步,同步锁使用Lock锁对象充当

  • Lock接口是控制多个线程对共享资源进行访问的工具,

代码如下:

package com.xjggb.security;


import java.util.concurrent.locks.ReentrantLock;

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

        /*
        * 面试题  synchronized与Lock的异同
        * 相同: 二者都可以解决线程安全问题
        * 不同: synchronized 执行完同步代码会自动释放锁
        *       lock需要手动的启动同步(lock()) 同时结束同步也需要手动的实现
        * */
        //创建子线程
        TestLock testLock = new TestLock();

        Thread thread1 = new Thread(testLock);
        Thread thread2 = new Thread(testLock);
        Thread thread3 = new Thread(testLock);
        thread1.start();
        thread2.start();
        thread3.start();


    }


}

class TestLock implements Runnable {

    private int ticket = 100; //定义票数
    private ReentrantLock lock = new ReentrantLock(); //实例化ReentrantLock


    @Override
    public void run() {

        try {
            lock.lock();  //开启锁
            while (true) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;

                } else {
                    break;
                }
            }
        } finally {
            lock.unlock();  //释放锁
        }

    }
}

9.同步练习

1.线程不安全
package com.xjggb.account;

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

        Account account = new Account(0);
        Customer customer1 = new Customer(account);
        Customer customer2 = new Customer(account);
        Customer customer3 = new Customer(account);

        customer1.setName("甲");
        customer2.setName("乙");
        customer3.setName("丙");

        //启动线程
        customer1.start();
        customer2.start();
        customer3.start();
        
             /*
        *  代码运行结果直接出现线程安全问题
        * 存钱成功,余额为2000.0
          存钱成功,余额为2000.0
          存钱成功,余额为2000.0
          存钱成功,余额为4000.0
          存钱成功,余额为5000.0
          存钱成功,余额为4000.0
          存钱成功,余额为8000.0
          存钱成功,余额为8000.0
          存钱成功,余额为8000.0
        * */


    }
}

//多线程用户存钱
class Customer extends Thread {

    private Account account;

    //创建有参构造赋值
    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {

        for (int i = 0; i < 3; i++) {

            account.setMoney(1000);
        }

    }
}


//创建账户类
class Account {

    private double money;

    public Account() {
    }

    public Account(double money) {
        this.money = money;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param
     */
    public void setMoney(double mone) {
        if (mone>0){

            //睡3秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {   
                e.printStackTrace();
            }
            money+=mone;  //睡了一秒,甲还没有反应过来,乙就进来了,这样就发生了线程安全问题

            System.out.println("存钱成功,余额为" + money);
        }
    }

}

2.线程安全的

三种方式

  1. synchronized方法
  2. synchronized代码块
  3. lock锁

三种方式都行

使用synchronized方法

package com.xjggb.account;

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

        Account account = new Account(0);
        Customer customer1 = new Customer(account);
        Customer customer2 = new Customer(account);
        Customer customer3 = new Customer(account);

        customer1.setName("甲");
        customer2.setName("乙");
        customer3.setName("丙");

        //启动线程
        customer1.start();
        customer2.start();
        customer3.start();


       /*
        *保证线程安全
        * 存钱成功,余额为1000.0
          存钱成功,余额为2000.0
          存钱成功,余额为3000.0
          存钱成功,余额为4000.0
          存钱成功,余额为5000.0
          存钱成功,余额为6000.0
          存钱成功,余额为7000.0
          存钱成功,余额为8000.0
          存钱成功,余额为9000.0
        * */


    }
}

//多线程用户存钱
class Customer extends Thread {

    private Account account;

    //创建有参构造赋值
    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {

        for (int i = 0; i < 3; i++) {

            account.setMoney(1000);
        }

    }
}


//创建账户类
class Account {

    private double money;

    public Account() {
    }

    public Account(double money) {
        this.money = money;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param
     */
    public synchronized   void setMoney(double mone) {
        if (mone>0){

            //睡3秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money+=mone;  //睡了一秒,添加synchronized 就像队列一样排队一个一个来

            System.out.println("存钱成功,余额为" + money);
        }
    }

}

10.线程通信

涉及到三个方法:

wait():一旦此执行此方法,当前线程就进入阻塞状态,并释放同步监视器

notify():一旦执行

代码如下:

package com.xjggb.account;

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

        Account account = new Account(0);
        Customer customer1 = new Customer(account);
        Customer customer2 = new Customer(account);
        Customer customer3 = new Customer(account);

        customer1.setName("甲");
        customer2.setName("乙");
        customer3.setName("丙");

        //启动线程
        customer1.start();
        customer2.start();
        customer3.start();


        /*
        *
        * 存钱成功,余额为1000.0
          存钱成功,余额为2000.0
          存钱成功,余额为3000.0
          存钱成功,余额为4000.0
          存钱成功,余额为5000.0
          存钱成功,余额为6000.0
          存钱成功,余额为7000.0
          存钱成功,余额为8000.0
          存钱成功,余额为9000.0
        * */


    }
}

//多线程用户存钱
class Customer extends Thread {

    private Account account;

    //创建有参构造赋值
    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {

        for (int i = 0; i < 3; i++) {

            account.setMoney(1000);
        }

    }
}


//创建账户类
class Account {

    private double money;

    public Account() {
    }

    public Account(double money) {
        this.money = money;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param
     */
    public synchronized   void setMoney(double mone) {
        if (mone>0){

            //睡3秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money+=mone;  //睡了一秒,甲还没有反应过来,乙就进来了,这样就发生了线程安全问题

            System.out.println("存钱成功,余额为" + money);
        }
    }

}

小结:

wait(),notify(),notifyAll() 三个方法必须使用在同步方法或者代码块中

wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器

否则就会出现异常

面试题:

sleep()和wait()异同?

  1. 相同点: 一旦执行方法就会使得当前的线程进入阻塞状态
  2. 不同点:
    • 两个方法的声明位置不同:Thread类中声明sleep(),Object类中声明wait()
    • 调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码中
    • 关于是否释放同步监视器(锁)如果两个方法都使用在同步代码块或者同步方法中,sleep()方法不会释放锁wait()会自动释放锁

11.生产者和消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。

代码如下

package com.xjggb.security;

public class Test07 {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();  //创建店员
        Productor productor = new Productor(clerk); //创建生产者
        productor.setName("我是生产者");
        Customer customer = new Customer(clerk); //创建生产者
        customer.setName("我是消费者");

        //启动线程
        productor.start();
        customer.start();


    }
}

//生产者
class Productor extends Thread {
    private Clerk clerk;

    //创建构造函数
    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }


    @Override
    public void run() {
        System.out.println("生产者开始生产产品");
        while (true) {
            //睡一秒中
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //开始生产产品
            clerk.addProduct();

        }

    }
}

//消费者
class Customer extends Thread {

    private Clerk clerk;

    //创建构造函数
    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            //睡一秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //开始消费产品
            clerk.getProduct();
        }

    }
}

//店员
class Clerk {

    private int product = 0;  //定义产品数量

    public synchronized void addProduct() {  //生产产品
        if (product < 20) {
            product++;
            System.out.println(Thread.currentThread().getName() + "生产了第" + product + "个");
            notifyAll();  //唤醒
        } else {
            try {
                wait();  // 阻塞线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public synchronized void getProduct() { //消费产品
        if (product > 0) {
            System.out.println(Thread.currentThread().getName() + "取走了第" + product + "个");
            product--;
            notifyAll();
        } else {
            try {
                wait();  // 阻塞线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }


}



小结:

  1. 分析是否是多线程问题?是,生产者和消费者
  2. 分析是否有共享数据,是,店员 (或者产品)
  3. 如何结局线程的安全问题?同步代码块,同步方法,和lock锁等
  4. 是否涉及线程通信

12.JDK5.0 新增线程创建方式

1. 新增方式一:实现Callable接口

与Runnable相比Callable功能更加强大

  • 相比于run()方法,call()可以有返回值
  • 方法可以抛出异常,被外面的操作捕获
  • 支持范型的返回值
  • 需要借助FutureTask类,比如获取返回结果

 Future接口

 可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。

 FutrueTask是Futrue接口的唯一的实现类

 FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

代码如下:

package com.xjggb.account;

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

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

        //创建子类线程
        Thread1 thread1 = new Thread1();

        //将实现类对象作为参数传递待FutureTask的构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(thread1);

        //将FutureTask的对象传到Thread的构造器中,创建Thread类的对象并start()
        Thread thread = new Thread(futureTask);
        //启动线程
        thread.start();
        //获取返回值
        try {
            System.out.println("偶数总和" + futureTask.get());

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}



class  Thread1 implements Callable {
    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 0; i < 100; i++) {

            if (i%2==0){  //获取模于2等于零的累加
                sum+=i;
            }
        }

        return sum;
    }
}

2.新增方式二:使用线程池

 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。

 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。

 好处:

 提高响应速度(减少了创建新线程的时间)

 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

 便于线程管理  corePoolSize:核心池的大小  maximumPoolSize:最大线程数

 keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程API

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

 ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

​ void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable

​ Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable

 void shutdown() :关闭连 接池  Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池  Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池  Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池  Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池  Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。

创建线程池

      //Executors工具类、线程池的工厂类,用法语创建并返回不同类型的线程池
        //1、创建一个可根据需要创建性线程的线程池
        Executors.newCachedThreadPool();

        //2、创建一个可重用固定线程书的线程池
        Executors.newFixedThreadPool(n);


        //3、创建一个只有一个线程的线程池
        Executors.newSingleThreadExecutor();

        //4、创建一个线程池,它可安排再给定延迟后运行命令或者定期地执行
        Executors.newScheduledThreadPool(n);

package com.xjggb.account;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test04 {

    public static void main(String[] args) {
        //创建指定的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //创建线程的实现对象
        PoolTest poolTest = new PoolTest();
        executorService.execute(poolTest);

        


    }
}

class PoolTest implements Runnable{
    @Override
    public void run() {

        int sum=0;
        for (int i = 0; i < 100; i++) {
            if (i%2==0){
                sum+=i;
            }
        }
        System.out.println("sum = " + sum);

    }
}

Callable

service.submit(new FutureTask(new PoolThread1()));

关闭连接池

   executorService.shutdown();

设置连接池属性

//类型强转
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        service1.setCorePoolSize(15);
        service1.setKeepAliveTime();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值