Java学习笔记14-多线程

多线程的认识

1、什么是程序

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

2、什么是进程

是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。如:运行中的QQ

3、什么是线程

一个进程内部可以执行多个任务,每个任务即为一个线程。
多线程是指一个进行内多个任务(线程)同时运行

4、线程的组成是什么

任何一个线程都具有一下基本的组成部分:

  • CPU时间片:操作系统(OS) 会为每个线程分配执行时间。

  • 运行数据:

    ​ 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。

    ​ 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

  • 线程的逻辑代码。

5、线程的特点是什么
  • 线程抢占式执行:

    ​ 效率高

    ​ 可防止单- -线程长时间独占CPU

  • 在单核CPU中,宏观上同时执行,微观上顺序执行。

6、什么是多线程

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。多线程技术使程序能够同时完成多项任务。多个任务并不是同时执行,而是分时间段交替执行。由于计算机执行速度快,人们感觉它们是同时执行的。

7、多线程的特点
  • 同时执行两个或多个任务;
  • 提高应用程序的响应。对图形化界面更有意义,可增强用户体验;
  • 提高计算机系统CPU的利用率;

线程的实现

实现方法

  • 通过Tread类来实现;
  • 通过Runnable接口实现多线程:
  • 实现Callable接口
1、Tread类实现
  • 创建对象
  • 用run()方法开启线程的逻辑代码;
  • start()启动线程;
public class MyTread extends Thread {

    public static void main(String[] args) {
        MyTread myTread=new MyTread();
        myTread.start();//开启线程
        for (int i = 0; i < 50; i++) {
            System.out.println("==主线程=="+i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("这是一个线程"+i);
        }

    }
}
2、Runnable接口实现
public class MyRunnable implements Runnable {
    //创建方式一
    @Override
    public void run() {
        for (int i = 0; i <20; i++) {
            System.out.println(Thread.currentThread().getName()+"----------"+i);
        }
    }

    //创建方式二
    Runnable runnable=new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println(Thread.currentThread().getName()+"-------"+i);
            }
        }
    };

    public static void main(String[] args) {

        MyRunnable myRunnable=new MyRunnable();
        Thread thread=new Thread(myRunnable,"我的线程1");
        thread.start();//开启线程

        Thread thread1=new Thread(myRunnable.runnable,"我的线程2");
        thread1.start();

        for (int i=0;i<30;i++){
            System.out.println(Thread.currentThread().getName()+"------主线程"+i);
        }
    }
}

线程的生命周期

在这里插入图片描述

一个完整的生命周期中通常要经历如下的五种状态

  • 新建:线程的创建;
  • 就绪:执行start()方法;
  • 运行:执行run()方法
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态;
  • 死亡:
    自然死亡(程序执行完或程序发生异常,程序结束)
    强制死亡(执行stop()方法,断电,杀掉进程)

表示状态的常用方法:

方法用途
sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
yield()暂停当前正在执行的线程对象,并执行其他线程
setPriority(int newPriority)更改线程的优先级
setName(String name)改变线程名称,使之与参数 name 相同
setDaemon(boolean on)将该线程标记为守护线程或用户线程
join()等待该线程终止
isDaemon()测试该线程是否为守护线程

yield()

public class YieldThread extends Thread {

    public static void main(String[] args) {
        YieldThread y1=new YieldThread();
        YieldThread y2=new YieldThread();
        y1.start();
        y2.start();
    }

    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
            Thread.yield();//主动放弃CPU,当前线程主动放弃时间片,回到就绪状态,竞争下一段时间
        }

    }
}

sleep(long millis)

public class SleepTread extends Thread{

    public static void main(String[] args) {
        SleepTread sleepTread=new SleepTread();
        sleepTread.setName("我的线程");
        sleepTread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程"+i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

setPriority(int newPriority)

public class PriorityThread extends Thread{

    public static void main(String[] args) {
        PriorityThread p1=new PriorityThread();
        PriorityThread p2=new PriorityThread();
        PriorityThread p3=new PriorityThread();
        p1.setName("p1");
        p2.setName("p2");
        p3.setName("p3");
        //设置启动级别,数字越大,级别越高,启动越早
        p1.setPriority(1);
        p2.setPriority(5);
        p3.setPriority(10);

        //启动
        p1.start();
        p2.start();
        p3.start();
    }


    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
        }
    }
}

join()

public class JoinThread extends Thread{

    public static void main(String[] args) {
        JoinThread j1=new JoinThread();
        try {
            j1.join();//加入当前线程,阻塞当前线程,直到当前线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        j1.start();

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"---主线程---"+i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

isDaemon()

public class DeamonThread extends Thread{

    public static void main(String[] args) {
        DeamonThread thread=new DeamonThread();
        thread.setDaemon(true);//设置线程为守护线程
        thread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程------"+i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
        }
    }
}

线程的安全

1、多线程的安全问题
  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
  • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
  • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
2、解决线程安全问题常用方法

同步(上锁):当多个线程同时访问同一个资源时,先让让其他线程等待,再让一个线程执行访问之后,再让其他中一个线程执行访问;

3、线程的同步(上锁)

1、实现方式:

  • 同步代码块:

    synchronized (临界资源对象) { //对临界资源对象加锁

    ​ //代码(原子操作)}

  • 同步方法:

    synchronized 返回值类型方法名称(形参列表0){ //对当前对象(this) 加锁

    //代码(原子操作)}

2、同步规则:

  • 只有在调用包含同步代码块的方法。或者同步方法时,才需要对象的锁标记。

  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

  • 已知JDK中线程安全的类:(以下类中的公开方法,均为synchonized修饰的同步方法。)

    StringBuffer

    Vector

    Hashtable

3、死锁:

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。(如:两个人吃饭各拿一只筷子,互不相让)
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

同步实现方式:

同步代码块:

/**
 * 同步代码块实现A执行B不执行,B执行A不执行
 */
public class test1 {
    private static int index=0;

    public static void main(String[] args) throws Exception{
        //创建数组
        String[] s=new String[5];
        //创建两个线程
        Runnable n1=new Runnable() {
            @Override
            public void run() {
                //同步代码块,可保证一个线程访问时,另一个线程等待
                synchronized (s){
                    s[index]="hello";
                    index++;
                }
            }
        };
        Runnable n2=new Runnable() {
            @Override
            public void run() {
                synchronized (s){
                    s[index]="world";
                    index++;
                }
            }
        };

        //创建两个线程对象
        Thread t1=new Thread(n1,"A");
        Thread t2=new Thread(n2,"B");
        //启动线程
        t1.start();
        t2.start();
        //加入线程
        t1.join();
        t2.join();

        System.out.println(Arrays.toString(s));
    }
}

同步方法:

/**
 * 通过同步方法,实现买票
 */
public class Test2Ticket implements Runnable {

    private int tickets=100;

    @Override
    public void run() {
        while (true){
            if (!sale()){
                break;
            }
        }
    }
    //卖票(同步方法)
    public synchronized boolean sale(){//锁指的是this,当为静态方法时,锁为类.class
        if (tickets<=0){
            return false;
        }
        System.out.println(Thread.currentThread().getName()+"卖了票的"+tickets+"张数");
        tickets--;
        return true;
    }

    public static void main(String[] args) {
        Test2Ticket domeTicket=new Test2Ticket();
        Thread win1=new Thread(domeTicket,"窗口1");
        Thread win2=new Thread(domeTicket,"窗口2");
        Thread win3=new Thread(domeTicket,"窗口3");
        Thread win4=new Thread(domeTicket,"窗口4");
        win1.start();
        win2.start();
        win3.start();
        win4.start();
    }
}

死锁:

男孩女孩各有一只筷子,吃饭问题

创建对象:

/**
 * 两个筷子吃饭问题
 */
public class Mylock {
    //创建两个锁
    public static Object a=new Object();
    public static Object b=new Object();
}

男孩

public class Boy extends Thread {
    @Override
    public void run() {
        synchronized (Mylock.a){//上锁

            System.out.println("男带拿到了a");
            synchronized (Mylock.b){
                System.out.println("男孩拿到了b");
                System.out.println("男孩可以吃饭了");
            }
        }
    }
}

女孩

public class Girl extends Thread {

    @Override
    public void run() {
        synchronized (Mylock.b){
            System.out.println("女孩有b");
            synchronized (Mylock.a){
                System.out.println("女孩拿到了a");
                System.out.println("女孩可以吃饭了");
            }
        }
    }
}

测试

public class TestDeadLock {

    public static void main(String[] args) {

        Boy boy=new Boy();
        Girl girl=new Girl();
        boy.start();
        girl.start();
    }
}

在这里插入图片描述

解决方法:在测试时让一个线程多休眠sleep()一会

public class TestDeadLock {

    public static void main(String[] args) {

        Boy boy=new Boy();
        Girl girl=new Girl();
        boy.start();
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        girl.start();
    }
}

在这里插入图片描述

4、线程的通信

1、简介:
在多线程运行时,每个线程之间了通过同步解各自的状态

2、线程通信时常用方法

  • 等待:
    wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问

  • 通知:
    notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待

    notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

3、可解决的问题:生产者,消费者

​ 若千个生产者在生产产品,这些产品将提供给若千个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

案例:面包生产与消费问题

面包类:

public class Bread {

    private int id;
    private String name;

    public Bread(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Bread() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Bread{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

存放面包容器

public class BreadCon {

    private Bread[] cons=new Bread[6];
    //存放面包位置
    private int index=0;

    //存放面包
    public synchronized void input(Bread b){
        //判断容器有没有满
        if (index>=6){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        cons[index]=b;
        System.out.println(Thread.currentThread().getName()+"生产了"+b.getId());
        index++;
        //唤醒消费者
        this.notify();
    }

    //取出面包
    public synchronized void output(){
        //判断容器有没有完
        if (index<=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        index--;
        Bread b=cons[index];
        System.out.println(Thread.currentThread().getName()+"消费了"+b.getId());
        cons[index]=null;
        //唤醒生产者
        this.notify();
    }
}

生产者

public class Product implements Runnable {

    private BreadCon con;

    public Product(BreadCon con) {
        super();
        this.con = con;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            con.input(new Bread(i,Thread.currentThread().getName()));
        }
    }
}

消费者

public class Consume implements Runnable {

    private BreadCon con;

    public Consume(BreadCon con) {
        this.con = con;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            con.output();
        }
    }
}

测试

public class TextBread {

    public static void main(String[] args) {

        //创建容器
        BreadCon con=new BreadCon();

        //生产和消费
        Product product=new Product(con);
        Consume consume=new Consume(con);

        //创建线程对象
        Thread t1=new Thread(product,"小明");
        Thread t2=new Thread(consume,"小hua");

        //启动线程
        t1.start();
        t2.start();
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值