0
点赞
收藏
分享

微信扫一扫

多线程学习心得

40dba2f2a596 2022-02-15 阅读 131

线程

线程简介

Process 和 Thread

程序:指令和数据的集合,本身不能运行(静态)

进程:执行程序的一次执行过程(动态)

线程:进程包含多个线程,是cpu调度和执行的单位

核心概念

  • 线程是独立的执行路径
  • 进程开辟多个线程,运行有调度器调度,调度器操作与OS紧密相关,先后顺序不能人为干预
  • 在同一资源操作时,存在资源抢夺问题,需要加入并发控制
  • 线程会带来额外开销,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程创建(Thread、Runnable、Callable)

三种创建方式

Thread(class) 继承Thread类

Runnable(interface) 实现Runnable接口

Callable(interface) 实现Callable接口(了解)

Thread

  1. 继承Thread类
  2. 重写run方法
  3. 主类调用start开启线程

注意:线程开启,不一定立即执行,cpu安排调度

package com.dd.demo01;

import java.util.Arrays;

//创建线程方式1:继承Thread类,重写run方法,调用start开启线程
public class TestThread01 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主线程

        //调用线程
        TestThread01 testThread01 = new TestThread01();

        //开启其他线程
        /*
        使用run方法,只有一条主执行路径
        使用start方法,多条执行路径,主线程与子线程并行交替执行
         */
        //testThread01.run();
        testThread01.start();

        //主方法体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习--" + i);
        }
    }
}

Runnable

  1. 实现Runnable接口
  2. 重写run方法
  3. 执行线程需要丢入Runnable接口实现类
  4. 调用start方法
package com.dd.demo01;

//实现Runnable接口方法:1.实现Runnable方法 2.重写run方法 3.执行线程需要丢入Thread线程池 4.执行start方法
public class TestThread02 implements Runnable{

    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("我在听music--" + i);
        }
    }
    public static void main(String[] args) {
        //进入主类

        //调用继承runnable方法
        TestThread02 testThread02 = new TestThread02();

        //创建线程对象,通过线程对象来开启线程,(代理思想)
        //Thread thread = new Thread(testThread02);
        //thread.start();

        new Thread(testThread02).start();

        for (int i = 0; i < 20; i++) {
            System.out.println("我在玩游戏--" + i);
        }
    }
}

Callable

  1. 实现callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutoService ser = Excutors.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get()
  7. 关闭服务:ser.shutdownNow()
public class TestCallable implements Callable<Boolean> {
    private String name;
    private String url;

    //创建一个构造方法
    public TestCallable(String url,String name){
        this.url = url;
        this.name = name;
    }

    //重写run方法
    @Override
    public Boolean call() {
        WebDownloader downloader = new WebDownloader();
        downloader.downloader(url,name);
        System.out.println("我下载了图片:" + name);
        return true;
    }

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

        TestCallable t1 = new TestCallable("https://img0.baidu.com/it/u=704351190,1261438373&fm=253&fmt=auto&app=138&f=JPEG?w=375&h=500","1.jpg");
        TestCallable t2 = new TestCallable("https://img1.baidu.com/it/u=2399855498,1383612751&fm=253&fmt=auto&app=138&f=JPEG?w=362&h=500","2.jpg");
        TestCallable t3 = new TestCallable("https://img0.baidu.com/it/u=3489763019,4289695603&fm=253&fmt=auto&app=138&f=JPEG?w=283&h=400","3.jpg");
        
        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> result1 = ser.submit(t1);
        Future<Boolean> result2 = ser.submit(t2);
        Future<Boolean> result3 = ser.submit(t3);
        
        //获取结果
        boolean r1 = result1.get();
        boolean r2 = result2.get();
        boolean r3 = result3.get();

        System.out.println(r1);
        System.out.println(r2);
        System.out.println(r3);

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

小结

继承Thread类

  1. 子类继承Thread类具备多线程能力
  2. 启动线程:子类对象.start()
  3. 不建议使用:避免OOP单继承局限性

实现Runnable接口

  1. 实现接口Runnable具有多线程能力
  2. 启动线程:传入目标对象+Thread对象.start()
  3. 推荐使用:避免单继承局限性,灵活方便,方便一个对象被多个线程使用

实现Callable接口(了解即可)

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

线程代理

静态代理模式

  1. 真实对象和代理对象都实现同一接口
  2. 代理对象代理真实对象

好处

  • 代理对象可以做真实对象做不了的事情
  • 真实对象专注做自己事情
package com.dd.staticProxy;

//真实对象和代理对象实现同一接口
public class staticProxy {
    public static void main(String[] args) {
        RealMe realMe = new RealMe();
        //插入一个多线程,使用lamda表达式
        new Thread(()-> System.out.println("我爱你")).start();

        WeddingCompany weddingCompany = new WeddingCompany(realMe);
        weddingCompany.marry();
    }
}

//创建一个结婚接口
interface Marry {
    void marry();
}

//真实对象
class RealMe implements Marry{
    @Override
    public void marry() {
        System.out.println("今天结婚了,超开心!");
    }
}

//代理对象
class WeddingCompany implements Marry{
    //目标的类型是真实对象和代理对象一起实现的接口
    private Marry target;

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

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

    private void before() {
        System.out.println("婚礼准备,布置会场!");
    }

    private void after() {
        System.out.println("婚礼结束,收尾款!");
    }
}

线程状态

线程进入停止状态

  1. 建议线程正常停止–>利用次数,不建议死循环

  2. 建议设置标志位–>设置一个标志位

  3. 不要使用stop和destory等过时或者JDK不建议使用的方法

package com.dd.state;

public class TestStop implements Runnable {

    //设置标识位
    private Boolean flag = true;

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

    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){
                testStop.stop();
                System.out.println("该线程停止了");
            }
        }
    }
}

注意

不可以使用(new Thread(new TestStop()).start();)和(new TestStop().stop();)可能会出现执行不了stop方法

线程休眠(Sleep)

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

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

public class TestSleep {

    public static void main(String[] args) {
        //获取当前系统时间
        Date date =new Date(System.currentTimeMillis());

        try {
            while (true) {
                Thread.sleep(1000);
                //打印当前时间
                System.out.println(new SimpleDateFormat("HH-mm-ss").format(date));
                //重新获取当前时间,防止时间一直重复
                date = new Date(System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    public static void tenDown() throws InterruptedException {
        int time = 10;
        while (true){
            Thread.sleep(1000);
            System.out.println(time--);
            if (time==0){
                break;
            }
        }
    }
}

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程由运行状态转换为就绪状态
  • 让cpu重新调度。礼让不一定成功
package com.dd.state;

public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

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

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

线程合并(Join)

  • 合并线程Join,待此线程执行完成,再执行其他线程,其他线程阻塞
  • 与插队类似
package com.dd.state;

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程vip来了" + 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 < 300; i++) {
            if (i == 200){
                thread.join();
            }
            System.out.println("main线程来了" + i);
        }
    }
}

总结

线程可以处于以下状态之一:

  • new:尚未启动的线程处于此状态

  • runnable:在java虚拟机中执行的线程处于此状态

  • blocked:被阻塞等待监视器锁定的线程处于此状态

  • waiting:正在等待另一线程执行特定动作的线程处于此状态

  • timed_waiting:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

  • terminated:已退出的线程处于此状态

    注意:线程进入死亡状态,不能再次启动

//观察线程的状态情况
package com.dd.state;

public class TestState {
    public static void main(String[] args) throws InterruptedException {
        //使用lambda表达式创建线程
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println("/");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread.State state = thread.getState();
        System.out.println(state);//new

        //观察启动后
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state);//打印启动后线程状态

        while (state != Thread.State.TERMINATED){
            Thread.sleep(100);
            System.out.println(state);
            //获取当前线程条件,结束循环
            state = thread.getState();
        }
    }
}

线程优先级

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

  • Thread.MAX_PRIORITY=10
  • Thread.MIN_PRIORITY=1
  • Thread.NORM_PRIORITY

线程改变和获取方式

  • new Thread.getPriority() -----获取线程的优先级

  • new Thread.setPriority(int xxx)-----设置当前线程的优先级,参数1-10

    注意:优先级的设定建议在start方法前面

package com.dd.priority;

public class TestSetPriority {
    public static void main(String[] args) {
        Mypriority mypriority = new Mypriority();
        //打印当前线程
        System.out.println(Thread.currentThread().getName() + "-->" + new Thread().getPriority());

        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);

        //线程默认优先级
        t1.start();

        t2.setPriority(4);
        t2.start();

        t3.setPriority(8);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);//Thread.MAX_PRIORITY=10
        t4.start();

        t5.setPriority(Thread.MIN_PRIORITY);//Thread.MIN_PRIORITY=1
        t5.start();
    }
}

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

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
package com.dd.state;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        Thread thread = new Thread(god);

        //设置god为守护线程,true为守护线程,false为用户线程
        thread.setDaemon(true);
        thread.start();

        //启动用户线程
        new Thread(new You()).start();
    }

}
//守护线程
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝守护着你!");
        }
    }
}
//用户线程
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("我在开心的活着!");
        }
        System.out.println("=====GoodBye.world=====");
    }
}

线程同步

并发

简介:同一个对象多个线程同时操作

线程同步:一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列。等待前面线程使用完毕,下个线程再使用。形成条件:队列+锁

锁机制:synchronized

存在的问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致较多的上下文切换和调度延时引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致性能倒置,引发性能问题

synchronized关键字包含2种用法:

  1. synchronized方法

  2. synchronized块(synchronized(obj){})

    obj称为同步监视器

    • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定obj。同步方法的obj指的是this,就是对象本身。
package com.dd.syn;

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

public class UnsafeList {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

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

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

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

}

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占用的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有”两个以上对象的锁“时,就可能发生死锁问题

产生死锁的四个必要条件

  1. 互斥条件:一个资源只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源。在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
package com.dd.syn;

public class TestDeadLock {

    public static void main(String[] args) {
        MakeUp g1 = new MakeUp("灰姑娘",0);
        MakeUp g2 = new MakeUp("白雪公主",1);

        g1.start();
        g2.start();
    }
}

class Lipchip{

}

class Mirror{

}

class MakeUp extends Thread{
    //获得镜子和口红
    static Lipchip lipchip = new Lipchip();
    static Mirror mirror = new Mirror();

    String girlName;
    int choice;

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

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void makeup() throws InterruptedException {
        if (choice == 0){
            synchronized (lipchip){
                System.out.println(girlName + "拿到了口红");
                Thread.sleep(1000);
            }
            synchronized (mirror){
                System.out.println(girlName + "拿到了镜子");
            }
        }else {
            synchronized (mirror){
                System.out.println(girlName + "拿到了镜子");
                Thread.sleep(2000);
            }
            synchronized (lipchip){
                System.out.println(girlName + "拿到了口红");
            }
        }
    }
}

线程协作

生产者消费者模式

管程法

package com.dd.pcQuestion;

//测试生产者消费问题--->管程法
//生产者 消费者 产品 缓存区
public class TheWay1 {

    public static void main(String[] args) {
       SynContainer synContainer =new SynContainer();

       new Productor(synContainer).start();
       new Consumer(synContainer).start();
    }

}

//生产者
class Productor extends Thread{
    SynContainer synContainer = new SynContainer();

    public Productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产者生产" + i + "只鸡" );
            synContainer.push(new Chicken(i));
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer synContainer = new SynContainer();

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费者消费了" + synContainer.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;
    }
}

信号灯法

package com.dd.pcQuestion;

//测试生产者消费问题--->信号灯法
public class TheWay2 {
    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 = !flag;
    }

    //观看
    public synchronized void watch(){
        //演员表演,观众观看
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了" + voice);
        this.notifyAll();
        //更改当前信号状态
        this.flag = !flag;
    }
}

线程池

线程池相关的API:ExecutorService和Executors

  • ExecutorService:真正的线程池接口。常见子类ThreadExecutor
  • Executors:工具类、线程池的工厂类。用于创建并返回不同类型的线程池
package com.dd.syn;

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

public class TestPool {

    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

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

        //关闭线程池
        service.shutdown();
    }
}

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

总结

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口
  3. 实现Callable接口
package com.dd.demo02;

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

public class TestNew {
    public static void main(String[] args) {
        new MyThread1().start();

        new Thread(new MyThread2()).start();

        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
        new Thread(futureTask).start();
        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//继承Thread
class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("我是MyThread1");
    }
}
//实现Runnable接口
class MyThread2 implements Runnable{
    @Override
    public void run() {
        System.out.println("我是MyThread2");
    }
}
//实现Callable接口
class MyThread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("我是MyThread3");
        return 100;
    }
}
举报

相关推荐

css学习心得

AlphaFold学习心得

Python学习心得1

学习心得03:OpenCV

机器学习心得(四)

MYSQL学习心得5

Unity学习心得1

0 条评论