Java多线程总结:进程与线程、线程的创建、线程状态与分类、线程方法、线程同步


一、进程(Process)与线程(Thread)

说的进程,先来提一下程序的概念:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态概念。
进程:进程是程序执行一次的过程,是一个动态概念,同时也是系统资源分配的基本单位。
线程:线程是一个独立执行的路径,它包含在进程之中,通常情况下一个进程可以包含若干个线程,我们熟知的main就是一个主线程,当然一个进程中至少有一个线程,不然它就没有存在的意义了,线程是CPU调度的基本单位。
再说说多线程,其实很多多线程都是模拟出来的,真正情况下的多线程是有多个CPU的,例如服务器等等,而 模拟出来的多线程,其实是在一个CPU的情况下,在同一时间点,CPU只能执行一个代码,但因为切换速度快,所以产生了同时执行的错觉。

相关知识:
1.程序运行时,即使自己没有创建线程,后台也会有多个线程,例如主线程main,垃圾回收线程gc等。
2.main()为主线程,是系统的入口,用于执行整个程序。
3.我们对同一份资源进行操作时,会存在资源抢夺问题,需要加入并发控制。
4.每个线程在自己的工作内存交互,内存控制不当会导致数据不一致。


二、线程的创建

创建方式:
继承Thread类
实现Runnable接口
实现Callable接口

1.继承Thread类

Thread实现了Runnable接口,要通过继承Thread类实现多线程,需要 分三步:
1.自定义线程类继承Thread类;
2.重写run()方法,编写线程执行体;
3.创建线程对象,调用start()方法启动线程。

代码示例:

//创建线程方式 继承Thread类

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 t1 = new TestThread1();
        //start方法开启线程
//        t1.run();
        t1.start();

        //main线程
        for (int i = 0; i < 500; i++) {
            System.out.println("我在玩游戏" + i);
        }
    }
}

结果:

在这里插入图片描述

我们可以知道,当我们直接调用t1.run()时,run方法因为没有并发性,所以会先执行run方法再执行for循环,而当我们通过t1.start()启动线程时,就会出现上述图片结果,两个线程交替执行,一般来说是t1线程比主线程慢,因为start()启动线程需要时间。调用run和start的区别可以参考下图:

在这里插入图片描述

2.实现Runnable接口

该方法需要 分三步:
1.定义MyRunnable类实现Runnable接口;
2.实现run()方法,编写线程执行体;
3.创建线程对象,调用start()方法启动线程。

代码示例:

//创建线程方式2 实现接口

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("看电视" + i);
        }
    }

    public static void main(String[] args) {

        //创建线程对象
        MyRunnable t = new MyRunnable();
        new Thread(t).start();
        
        //main线程
        for (int i = 0; i < 500; i++) {
            System.out.println("我在玩游戏" + i);
        }
    }
}

二者比较:
继承Thread类:不建议使用,有单继承局限性,如果使用了就不能继承其他类了。
实现Runnabke类:推荐使用,避免了单继承局限性,灵活方便,方便同一个对象被多个线程使用。

一个对象被多个线程使用图示:
在这里插入图片描述

一个对象被多个线程使用代码示例:买火车票案例,该案例里面是有错误的,线程会抢夺相同的资源,大家运行一下就知道了,后续会说:


//多个线程同时操作一个对象
//买火车票例子

public class TestThread3 implements Runnable{

    private int ticket = 20;

    @Override
    public void run() {
        while (true){
            if (ticket <= 0){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Thread.currentThread().getName()获取当前线程的调用者
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticket-- + "张票");
        }
    }

    public static void main(String[] args) {
        TestThread3 t = new TestThread3();

        new Thread(t,"小菜").start();
        new Thread(t,"小许").start();
        new Thread(t,"小困").start();
    }
}

3.实现Callable接口

该方法(暂时了解即可,后续可能会补充)需要 步骤为:
1.实现Callable接口,需要返回值类型;
2.重写call方法,需要抛出异常;
3.创建目标对象;
4.创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行:Future result1 = ser.submit(t1);
6.获取结果:boolean r1 = result1.get();
7.关闭服务:ser.shutdownNow()。


三、线程的状态和分类

1.线程的状态

线程的五个状态及其转化图:
在这里插入图片描述

线程状态。 线程可以处于以下状态之一:
NEW :尚未启动的线程处于此状态。
RUNNABLE :在Java虚拟机中执行的线程处于此状态。
BLOCKED :被阻塞等待监视器锁定的线程处于此状态。
WAITING :正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING :正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED :已退出的线程处于此状态。

PS:线程不能启动两次

2.线程的分类

线程可以分为 用户线程守护线程,虚拟机必须保证用户线程执行完毕,不用等待守护线程执行完毕,守护线程诸如后台操作日志、监控内存、垃圾回收线程gc等等。


四、线程的方法

线程的常用方法:

在这里插入图片描述

1.线程的停止

代码示例:

//测试stop

//1.建议线程正常停止 利用次数 不建议死循环
//2.建议使用标志位 设置一个标志位
//不要使用stop或者destroy等过时或者JDK不建议使用的方法

public class TestStop implements Runnable {

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

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

    //设置一个公开的方法停止线程
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop t = new TestStop();
        new Thread(t).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main : " + i);
            if (i == 900) {
                t.stop();
                System.out.println("线程该停止了!");
            }
        }
    }
}

2.线程的休眠

使用sleep函数,该函数以ms为单位。同时需要知道:每个对象都有一把锁,sleep不会释放锁。

代码示例:

//模拟倒计时

public class TestSleep_1 {

    public static void main(String[] args) {
        tenDown();
    }

    public static void tenDown() {
        int num = 10;
        while (true) {
            //延时1s 每秒输出一次
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(num--);
            if (num == 0) {
                break;
            }
        }
    }
}

3.线程礼让

线程礼让使用yield方法,调用该方法时,让当前正在执行的线程暂停,但不进入阻塞状态,而是重新变为就绪状态,让CPU重新调度,所以说,线程礼让是不一定会成功的,得看CPU的心情,比如,你调用了yield,下次CPU依旧调度你,这是可能发生的。

4.线程插队

join方法可以使其他线程阻塞,优先执行本线程,本线程执行完在执行其他线程。

代码示例:

//测试join方法 类似插队

public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println("VIP线程--" + i);
        }
    }

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

        //启动
        TestJoin t = new TestJoin();
        Thread thread = new Thread(t);
        thread.start();

        //主线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("main线程--" + i);
            if (i == 50) {
                thread.join();
            }
        }
    }
}

五、线程同步

1.线程同步机制、锁机制、死锁

线程同步机制:在多线程问题上,若多个线程访问同一个对象,并且有可能修改这个对象,这时候就需要线程的同步。线程同步其实就是一种等待机制,多个需要同时访问这个对象的线程,进入这个对象的等待池形成队列,等待前面的一个线程使用完毕,下一个线程在继续使用。
线程同步的形成条件需要队列+

锁机制:由于同一个进程的多个线程访问同一个存储空间,带来方便的同时也带来了冲突,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他的线程必须等待,使用后释放锁机可。

死锁:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

2.同步方法和同步块

synchronized方法: public synchronized void method(int args){};
synchronized块: synchroized (ojb){};

区别:
同步方法默认用this或当前类class对象作为锁;同步代码块可以选择以什么来加锁,比同步方法要更细,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;不太理解往这走–synchronized同步方法与同步块

3.简单的买票案例

线程不安全:


//线程不安全的买票案例

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

        BuyTicket b = new BuyTicket();

        new Thread(b, "小菜").start();
        new Thread(b, "小徐").start();
        new Thread(b, "小困").start();
    }
}

class BuyTicket implements Runnable {

    private int tickets = 20;
    boolean flag = true;

    @Override
    public void run() {

        while (flag) {
            try {
                //模拟一下延时
                Thread.sleep(100);
                buyTicket();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void buyTicket() throws InterruptedException {

        if (tickets <= 0) {
            flag = false;
            return;
        }

        System.out.println(Thread.currentThread().getName() + "获得了第" + tickets-- + "张票");
    }
}

synchronized方法:

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

        BuyTicket b = new BuyTicket();

        new Thread(b, "小菜").start();
        new Thread(b, "小徐").start();
        new Thread(b, "小困").start();
    }
}

class BuyTicket implements Runnable {

    private int tickets = 20;
    boolean flag = true;

    @Override
    public void run() {

        while (flag) {
            try {
                //模拟一下延时
                Thread.sleep(100);
                buyTicket();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void buyTicket() throws InterruptedException {

        if (tickets <= 0) {
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + "获得了第" + tickets-- + "张票");
    }
}

synchronized同步代码块:

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

        BuyTicket b = new BuyTicket();

        new Thread(b, "小菜").start();
        new Thread(b, "小徐").start();
        new Thread(b, "小困").start();
    }
}

class BuyTicket implements Runnable {

    private int tickets = 20;
    boolean flag = true;

    @Override
    public void run() {

        while (flag) {
            try {
                //模拟一下延时
                Thread.sleep(100);
                buyTicket();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void buyTicket() throws InterruptedException {
        synchronized ((Object) tickets) {
            if (tickets <= 0) {
                flag = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "获得了第" + tickets-- + "张票");
        }
    }
}

4.Lock锁

Lock是一个显示锁,需要手动开启手动关闭,不同于synchronized是一个隐式锁(出了作用域自动释放),Lock锁的性能高于synchronized锁,在使用时,选择优先级为:Lock > 同步代码块 > 同步方法。

使用格式:

        //加锁
        lock.lock();
        try {
        //需要保证线程安全的代码
            }
        } finally {
            //解锁
            lock.unlock();
        }

买票案例改:


import java.util.concurrent.locks.ReentrantLock;

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

        Buy b = new Buy();

        new Thread(b, "小菜").start();
        new Thread(b, "小徐").start();
        new Thread(b, "小困").start();
    }
}

class Buy implements Runnable {

    private int tickets = 20;
    //定义锁
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                //模拟延时
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                //加锁
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "获得了第" + tickets-- + "张票");
                } finally {
                    //解锁
                    lock.unlock();
                }
                
            } else {
                break;
            }
        }
    }
}

5.线程通信:生产者消费者问题

问题介绍 -> 生产者消费者问题

图示:
在这里插入图片描述

代码实现:

package Synchronized;

public class TestPC {
    public static void main(String[] args) {
        SynContainer c = new SynContainer();
        new Producter(c).start();
        new Consumer(c).start();
    }
}

//缓冲区
class SynContainer {

    private String[] s = new String[10];
    //记录个数
    private int idx = 0;

    //供生产者调用
    public synchronized void push(String str) {
        //缓冲区满了 生产者等待
        if (idx == s.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //没满
        s[idx++] = str;
        //通知消费者可以消费了
        this.notify();
    }

    //供消费者调用
    public synchronized String pop() {
        //如果缓冲区没有产品 消费者等待
        if (idx == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有产品
        idx--;
        String product = s[idx];
        //通知生产者可以生产了
        this.notify();
        return product;
    }

    public String[] getS() {
        return s;
    }
}

//生产者
class Producter extends Thread {

    private SynContainer container;

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

    public void run() {
        for (int i = 0; i < container.getS().length; i++) {
            String producter = "产品" + i;
            container.push(producter);
            System.out.println("生产了:" + producter);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer extends Thread {

    private SynContainer container;

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

    public void run() {
        for (int i = 0; i < container.getS().length; i++) {
            String consumer = container.pop();
            System.out.println("消费了:" + consumer);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

六、总结

其实没啥总结,边听课边记录的,感觉深度应该是不太够的,应该只能做做基础,这块好像是面试的重点,后续应该还要重新深入学习。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木是木木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值