多线程

概念

程序 进程 线程

1.程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

2.进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

3.线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

背景

以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?

多线程程序的优点:

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程

1.程序需要同时执行两个或多个任务。
2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3.需要一些后台运行的程序时。

一、方式一:继承Thread类

//1、定义子类继承Thread类
class MyThread extends Thread{
    //2、子类中重写Thread类中的run方法
    @Override
    public void run() {
        int i=0;
        while(i<100)
        {
            i++;
            if(i%2==0) System.out.println(i);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //3、创建Thread子类对象,即创建了线程对象。
        MyThread t1 = new MyThread();
        //4、调用线程对象start方法:启动线程,调用run方法
        t1.start();
        //同时执行
        int i=0;
        while(i<100)
        {
            i++;
            if(i%2==0) System.out.println(i+"main");
        }
    }
}

提示:

  1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
  3. 想要启动多线程,必须调用start方法。
  4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。

二、Thread常用方法

 public MyThread(String name){super(name);}
MyThread t1 = new MyThread("分");//构造器命名
Thread.currentThread().setName("主线程");//方法命名
if(i%2==0) System.out.println(i+Thread.currentThread().getName());


yield释放执行权 但cpu未必领情 可能又分配到同一线程。

 if(i==20) {//主线程中 到20时 会让分线程先走完 自己在走
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
 try {
                this.sleep(1000);//+ 1 s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
if(i%2==0) System.out.println(i+Thread.currentThread().getName()+t1.isAlive());//到20时都还活着

三、优先级

线程的优先级等级

MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级

说明

线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

Thread.currentThread().setPriority(MIN_PRIORITY);
System.out.println(i+Thread.currentThread().getName()+Thread.currentThread().getPriority());

四、方式二

class MThread implements Runnable{//1) 定义子类,实现Runnable接口。
    @Override
    public void run() {//2) 子类中重写Runnable接口中的run方法。
        for(int i=1;i<=100;i++)
        {
            if(i%2==0) System.out.println(i);
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread t1 = new Thread(mThread);/
        // /  3) 通过Thread类含参构造器创建线程对象。
        //4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
        t1.start();//  5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

    }
}


五、生命周期


六、线程同步

问题:出现重票、 错票(0、-1)
原因:某个线程操作尚未完成时 其他线程参与 也进行操作


class  Window implements Runnable{

    private int ticket=100;
    @Override
    public void run() {
        while(true){
            if(ticket>0){
             try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName()+":卖出票号为"+ticket--);
            }
            else break;
        }
    }
}
public class WindowTest {
    public static void main(String[] args) {
        Window w1=new Window();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);
        t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");
        t1.start();t2.start();t3.start();
    }
}

解决 :同步机制
我的理解是 t1/t2/t3 抢着进一个通道 只有当一个进程走完后 才能继续抢
方式一同步代码块

 Object obj=new Object();//任何类的对象都能充当锁 
    private int ticket=100;
    @Override
    public void run() {
        while(true) {
            synchronized (obj) {//小括号里是同步监视器(锁)
//大括号包住 要同步的代码
                }
}

方式二 同步方法

public synchronized  void run()

释放锁
wait break return 异常
不释放锁
sleep yield

七、死锁

原因

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

public class SiSuo {
    public static void main(String[] args) {
        final StringBuffer s1=new StringBuffer();
        final StringBuffer s2=new StringBuffer();
      /*  1.内部类中使用但未声明的任何局部变量必须在内部类的正文之前明确分配。
        Java匿名内部类的方法中用到的局部变量都必须定义为final
        3.在JVM中,内部类不是直接调用方法的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。
*/
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);//拿着s1锁时 让另一个线程拿s2 却发现无法进s1 导致其无法结束
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);

                    }
                }
            }
        }.start();
        new Thread(new Runnable(){//匿名实现runnable接口 注意Thread的括号
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);

                    }
                }
            }
        }).start();
    }
}

解决方法 Lock

  • 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增
    1. 面试题:synchronized 与 Lock的异同?
  • 相同:二者都可以解决线程安全问题
  • 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
  •    Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
    
  • 2.优先使用顺序:
  • Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)
 private ReentrantLock lock=new ReentrantLock();//1.实例化ReentrantLock
    @Override
    public   void run() {
        while(true) {
            try{
                lock.lock();//2.调用锁定方法
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖出票号为" + ticket--);
                } else break;
            }finally {
                lock.unlock();//3.调用解锁方法
            }
        }
    }
class Customer extends  Thread{//例
    private Account acct;
    public Customer(Account acct) {
        this.acct = acct;
    }
}
  public static void main(String[] args) {
        Account acct = new Account(0);
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);
}

线程通信

wait notify

 while(true){
               synchronized (this){
                if(num<=100){
                    notify();
                    System.out.println(Thread.currentThread().getName()+num);
                    num++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                else break;
            }
        }
  • 涉及到的三个方法:
  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
  • 说明:
  • 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。(你必须得有锁啊! 你还能隔空锁门?)
  • 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
  • 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
  • 面试题:sleep() 和 wait()的异同?
  • 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
  • 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
  •      2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
    
  •      3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁(死锁),wait()会释放锁。
    

生产者消费者例题


/**
 * 线程通信的应用:经典例题:生产者/消费者问题
 *
 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
 * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
 * 会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品
 * 了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
 *
 * 分析:
 * 1. 是否是多线程问题?是,生产者线程,消费者线程
 * 2. 是否有共享数据?是,店员(或产品)
 * 3. 如何解决线程的安全问题?同步机制,有三种方法
 * 4. 是否涉及线程的通信?是
 *
 * @author shkstart
 * @create 2019-02-15 下午 4:48
 */
class Clerk{

    private int productCount = 0;
    //生产产品
    public synchronized void produceProduct() {

        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");

            notify();

        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //消费产品
    public synchronized void consumeProduct() {
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();
        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Producer extends Thread{//生产者

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品.....");

        while(true){

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

            clerk.produceProduct();
        }

    }
}

class Consumer extends Thread{//消费者
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始消费产品.....");

        while(true){

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

            clerk.consumeProduct();
        }
    }
}

public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        Consumer c2 = new Consumer(clerk);
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();

    }
}
posted @ 2021-10-07 20:38  liv_vil  阅读(16)  评论(0)    收藏  举报