Java-34 多线程

进程和线程:

  • 进程:

  正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源

  • 线程:

  是进程中的单个顺序控制流,或者说就是一个单独执行路径
  一个进程如果只有一条执行路径,称之为单线程程序,一个进程如果有多条执行路径,称之为多线程程序
  线程是包含在进程中的

 

 并行和并发:

  • 并发:指的是逻辑上同时发生,指在某一时间段内同时运行多个程序
  • 并行:指的是物理上同时发生,指在某一时间点上同时运行多个程序

 Java程序的运行原理:

  java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。
在此之前的所有程序都是单线程的

注意:JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

 

 

 创建线程的方式:

  创建线程的第一种方式:继承Thread类

  创建线程的第二种方式:实现Runnable接口

  创建线程的第二种方式:实现Callable接口

继承Thread类:

/**
 *  为什么要重写run()方法呢?
 *      只有当需要被线程执行的时候,再把需要的代码加入到run()方法里
 *
 */
public class MyThread1 extends Thread {
    @Override
    public void run() {
        //写我们自己逻辑代码
//        System.out.println("数加学院,yyds");
        //一般来说,被线程执行的代码都是比较耗时的,为了模拟耗时,我这里用循环模拟
        for(int i =0;i<10;i++){
            System.out.println(i);
        }
    }
}

 

public class MyThreadDemo2 {
    public static void main(String[] args) {
        //创建子类对象
        //每new一次就相当于创建一个线程
//        MyThread1 myThread1 = new MyThread1();

        //模拟多线程环境
        //至少创建2个及以上的线程数
        MyThread1 myThread1 = new MyThread1();
        MyThread1 myThread2 = new MyThread1();

        myThread1.start();
        myThread2.start();
    }
}

 

注意:调用run()与调用start()的区别

run()的调用,仅仅是封装了被线程执行的代码,但是直接调用的话是普通的方法调用
start()方法的调用,首先单独启动了一个线程,然后再由JVM去调用该线程的run()方法

获取和设置线程名字:

  • 通过构造方法给线程起名字:

  Thread(String name) 分配一个新的 Thread对象。创建一个带参数的构造方法,参数传递线程名称,调用父类的带参构造方法,把线程名称传递给父类,让父类給子线程起一个名字

public class MyThread2 extends Thread{
    public MyThread2() {
    }

    public MyThread2(String name) {
        super(name);
    }

    @Override
    public void run() {
        //获取线程的名称
        String name = Thread.currentThread().getName();
        System.out.println(name);
    }
}

 

  • 通过方法给线程起名字:

  void setName(String name) 将此线程的名称更改为等于参数 name 。

       //通过构造方法给线程起名字
//        MyThread2 myThread2 = new MyThread2("小花");
//        myThread2.start();
        
        //void setName(String name) 将此线程的名称更改为等于参数 name 。
//        MyThread2 myThread1 = new MyThread2();
//        myThread1.setName("小瓜");       

 

  • 获取线程名字:

  Thread类中提供了一个方法叫getName()
  public static Thread currentThread()返回对当前正在执行的线程对象的引用

System.out.println(Thread.currentThread().getName());

  多线程实现原理:

 线程调度:

  • 分时调度模型   所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

Java使用的是抢占式调度模型

设置和获取线程优先级

public final int getPriority()

public final void setPriority(int newPriority)

/*
            获取线程的优先级
            public final int getPriority()返回此线程的优先级。

            设置线程的优先级
            public final void setPriority(int newPriority)更改此线程的优先级
            参数newPriority的范围为1-10之间
            newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY

            总结:
                1、线程的默认优先级为5
                2、线程优先级的范围是1-10
                3、线程优先级高仅仅表示的是获取CPU时间片的几率会高一些,但是不是绝对一定会获取


 */
public class MyPriorityThreadDemo {
    public static void main(String[] args) {
        MyPriorityThread t1 = new MyPriorityThread();
        MyPriorityThread t2 = new MyPriorityThread();

        //获取t1和t2的优先级
        System.out.println(t1.getPriority());//5
        System.out.println(t2.getPriority());//5


        //IllegalArgumentException
        t1.setPriority(1);
        t2.setPriority(10);

        t1.start();
        t2.start();
    }
}

线程控制:

线程休眠 public static void sleep(long millis)

public class  MySleepThread extends Thread{
    @Override
    public void run() {
        for(int i =0;i<100;i++){
            System.out.println(getName()+":"+i);

            //加入休眠方法
            //睡一秒钟,1秒=1000毫秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程加入 public final void join()

/*
 加入线程:
            public final void join():其他线程等待这个线程死亡

            注意事项:
                在线程设置为加入线程之前,先将该线程变为就绪状态,也就是调用start()方法
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        MySleepThread t1 = new MySleepThread();
        MySleepThread t2 = new MySleepThread();
        MySleepThread t3 = new MySleepThread();

        t1.setName("小花");
        t2.setName("小草");
        t3.setName("小树");

        t1.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
        t3.start();
    }
}

线程礼让 public static void yield()

public class  MySleepThread extends Thread{
    @Override
    public void run() {
        for(int i =0;i<10;i++){
            System.out.println(getName()+":"+i);
            System.out.println("--------------");
            Thread.yield();
        }
    }
}
/*
  礼让线程:
            public static void yield()
            暂停当前正在执行的线程对象,并执行其他线程
            它的作用是为了让多个线程之间更加和谐一点,并不能一定保证多个线程一人一次执行
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        MySleepThread t1 = new MySleepThread();
        MySleepThread t2 = new MySleepThread();
        MySleepThread t3 = new MySleepThread();

        t1.setName("小花");
        t2.setName("小草");

        t1.start();
        t2.start();

    }
}

后台线程 public final void setDaemon(boolean on)必须在线程启动前设置

Java中有两类线程:用户线程守护线程(后台线程)

用户线程:我们在学习多线程之前所有程序代码,运行起来都是一个个的用户线程。

守护线程:所谓的守护线程,指的就是程序运行的时候在后台提供了一种通用服务的线程。比如说
垃圾回收线程,它就是一个称职的守护者,并且这种线程并不属于程序不可或缺的部分。所以
非守护线程结束的时候,程序也就终止了,同时会杀死进程种所有的守护线程
反过来说,只要程序中存在非守护线程,程序就不会终止。

/*
  后台线程:(守护线程)
            public final void setDaemon(boolean on)
            通过这个方法将该线程对象标记为守护线程或者非守护线程。
            当运行的程序只有一个线程且是守护线程的时候,Java虚拟机退出

            注意:
                将线程设置为守护线程这一步骤,必须在启动前设置。
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        MySleepThread t1 = new MySleepThread();
        MySleepThread t2 = new MySleepThread();
        MySleepThread t3 = new MySleepThread();

        //小花线程死亡后程序结束,虚拟机退出,守护线程死亡
        t1.setName("小花");
        t2.setName("小草");
        t2.setDaemon(true);
        t3.setName("小树");
        t3.setDaemon(true);

        t1.start();
        t2.start();
        t3.start();
    }
}

中断线程 public final void stop()

    public void interrupt()

import java.util.Date;

public class MyStopThread extends Thread {
    @Override
    public void run() {
        System.out.println("开始执行时间:" + new Date());

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

        System.out.println("结束时间:" + new Date());
        System.out.println("hello");
    }
}
/*
        中断线程:
            public final void stop():让正在运行的线程停止,run方法剩下的代码不会执行,这个方法过时了,被弃用了,但是还能使用。
            public void interrupt():中断正在运行的线程,被中断的线程将run方法执行完毕,并抛出异常
            java.lang.InterruptedException: sleep interrupted

 */
public class ThreadStopDemo {
    public static void main(String[] args) {
        MyStopThread t1 = new MyStopThread();

        t1.start();

        try {
            Thread.sleep(3000);
            t1.stop();
//            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }



    }
}

线程的生命周期图

实现Runnable接口:

1、创建自定义类实现Runnable接口

2、重写run()方法

public class MyRunnbale1 implements Runnable {
    @Override
    public void run() {
        for (int i =1;i<=100;i++){
            //由于实现的Runnable接口中并没有getName()方法,所以这里无法使用Thread类中的
            //getName()方法
            //间接调用,因为currentThread()是静态的,可以直接通过类名调用获取当前正在
            //执行run方法的线程对象(Thread类型),所以可以调用getName()
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

3、创建自定义类的对象
4、创建Thread类的对象,将第三步创建的自定义类对象作为参数传递到构造方法中

public class MyRunnableDemo1 {
    public static void main(String[] args) {
        //创建自定义类的对象
        MyRunnbale1 myRunnbale1 = new MyRunnbale1();

        //创建Thread类的对象
        Thread t1 = new Thread(myRunnbale1);
        Thread t2 = new Thread(myRunnbale1);

        t1.setName("小花");
        t2.setName("小草");
        t1.start();
        t2.start();

    }
}

 简化代码:

  匿名内部类方法:

/*
匿名内部类方式实现线程的创建

格式:

        new 父类/接口(){
            重写父类和接口中的方法
        }
 */
public class MyThreadDemo3 {
    public static void main(String[] args) {
        //线程的父类Thread
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+"---"+"lycc");
                }
            }
        }.start();

        //线程的接口
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+"---"+"余路");
                }
            }
        }).start();

    }
}

二、解决线程安全问题:电影票案例

判断一个程序是否存在线程安全的标准
  1、是否存在多线程环境
  2、是否存在共享数据/共享变量
  3、是否有多条语句操作着共享数据/共享变量

例:

解决问题的思想:
  要是有一个办法可以让多条语句控制共享数据包成一个整体,在某个线程执行的时候,别的线程进不来
知道某个线程执行完毕一次run方法之后,别的线程再进来。

解决问题的方式:同步安全机制

1、使用同步代码块;

  格式:
    synchronized(对象){
     需要同步的代码;
    }

public class TicketWindow4 implements Runnable {
    //定义100张票
    private int tickets = 100;

    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //窗口1,窗口2,窗口3
            synchronized (obj) {//起到了一个锁的作用
                if (tickets > 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
                }
            }
        }

    }
}
public class SellTicketDemo4 {
    public static void main(String[] args) {
        //创建Runnable对象
        TicketWindow4 ticktetWindow2 = new TicketWindow4();

        //创建三个线程对象,模拟三个窗口
        Thread window1 = new Thread(ticktetWindow2, "窗口1");
        Thread window2 = new Thread(ticktetWindow2, "窗口2");
        Thread window3 = new Thread(ticktetWindow2, "窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

 2、定义一个同步方法

注意:

如果锁对象是this,就可以考虑使用同步方法。 否则能使用同步代码块的尽量使用同步代码块。

public class RunableDemo2 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        //同步方法中的锁对象就是new runabledemo2,也就是this
        System.out.println("this" + this);

        while (true) {
            run1();
        }

    }

    public synchronized void run1() {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票");
            ticket--;
        }
    }

//    public /*synchronized*/ void run1() {
//        synchronized (this) {
//            if (ticket > 0) {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票");
//                ticket--;
//            }
//        }
//
//    }
}
public class RunableText2 {
    public static void main(String[] args) {
        //创建runable实例对象,多线程共享电影票资源
        RunableDemo2 runableDemo2 = new RunableDemo2();
        System.out.println("run"+runableDemo2);
        //创建多线程
        Thread t0 = new Thread(runableDemo2);
        Thread t1 = new Thread(runableDemo2);
        Thread t2 = new Thread(runableDemo2);

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

 定义一个同步静态方法:

/*
同步方法的静态形式
 */
public class RunableDemo3 implements Runnable {
    private static int ticket = 100;

    @Override
    public void run() {
       
        while (true) {
            run1();
        }

    }

    //静态方法访问静态变量
    //这里的锁对象是本类的.class
    public static/* synchronized */void run1() {
        synchronized (RunableDemo3.class){
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

3、Lock锁:

我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock


import java.util.concurrent.locks.ReentrantLock;

public class RunableDemo4 implements Runnable {
private int ticket = 100;
ReentrantLock r = new ReentrantLock();

@Override
public void run() {

// while (true) {
// r.lock();
// if (ticket > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票");
// ticket--;
// r.unlock();
// }
//
// }


//通过finally代码块无论程序是否异常都释放资源
while (true) {
try {
r.lock();
if (ticket > 0) {//t1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket-- + "张票");
}
}finally {
r.unlock();
}

}

}

}
 
public class RunableText4 {
    public static void main(String[] args) {
        //创建runable实例对象,多线程共享电影票资源
        RunableDemo4 runableDemo4 = new RunableDemo4();
        //创建多线程
        Thread t0 = new Thread(runableDemo4);
        Thread t1 = new Thread(runableDemo4);
        Thread t2 = new Thread(runableDemo4);

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

同步的弊端:
  1、效率低
  2、容易产生死锁

/*
        死锁问题:
            两个或者两个以上的线程在争夺资源的过程中,出现了相互等待的现象,叫死锁现象。
        举例:
            中国人,美国人吃饭的问题。
            正常情况下:
                中国人:两支筷子
                美国人:一把刀,一把叉
            现在:
                中国人:一支筷子,一把刀
                美国人:一支筷子,一把叉
 */
public class DieLockDemo {
    public static void main(String[] args) {
        DieLock d1 = new DieLock(true);
        DieLock d2 = new DieLock(false);

        d1.start();
        d2.start();

    }
}
public class DieLock extends Thread {

    private boolean flag;

    public DieLock(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            //d1
            synchronized (MyLock.lock1){
                System.out.println("if lock1");//情况是d1走到这里等死锁
                synchronized (MyLock.lock2){
                    System.out.println("if lock2");
                }
            }
        }else {
            //d2
            synchronized (MyLock.lock2){//d2
                System.out.println("else lock2");//当d2走到这里等死锁
                synchronized (MyLock.lock1){
                    System.out.println("else lock1");
                }
            }
        }

    }
}


class MyLock {
    public static final Object lock1 = new Object();
    public static final Object lock2 = new Object();
} 

回顾以前的线程安全的类

      A:StringBuffer

      B:Vector

      C:HashTable

      D:如何把一个线程不安全的集合变成一个线程安全的集合类

          用Collections 工具类的方法即可。

以List集合为例:其他集合创建方法类似,具体看API

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class CollectionsSafety {
    public static void main(String[] args) {
        //这样创建的集合是不安全的
        List<String> list1 = new ArrayList<String>();

        //Collections工具类提供的方法,同步安全的
        List<String> list2 = Collections.synchronizedList(new ArrayList<String>());

        list2.add("1");
        list2.add("2");

        synchronized (list2){
            Iterator<String> iterator = list2.iterator();
            while(iterator.hasNext()){
                System.out.println(iterator.next());
            }
        }


//        for (String s : list2) {
//            System.out.println(2);
//        }
    }
} 

线程之间的通信问题(不同种类的线程针对同一资源的操作)

  以学生作为资源来实现的

      资源类:student

      设置数据类:SetThread(生产者)

      获取数据类:GetThread(消费者)

      测试类:StudentDemo

 代码:

基础版本只有一个数据

Student类

public class Student {
    String name;
    int age;
}

SetThread类

public class SetThread implements Runnable{
    private Student s;

    //为了保证多个线程操作同一个学生,重写有参构造方法
    public SetThread(Student s) {
        this.s = s;
    }


    @Override
    public void run() {
        s.name="lyc";
        s.age=18;
    }
} 

Getthread类

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        System.out.println(s.name + "---" + s.age);
    }
}

测试类:

public class StudentDemo {
    public static void main(String[] args) {
        Student student = new Student();
        SetThread setThread = new SetThread(student);
        GetThread getThread = new GetThread(student);

        Thread thread = new Thread(setThread);
        Thread thread1 = new Thread(getThread);

        thread1.start();
        thread.start();
    }
}

但是运行多次我们发现,有出现姓名为null或者年龄为0的情况出现

 

 这是因为CPU进行的操作都是原子操作,有情况是当对年龄进行赋值,还没来得及对姓名赋值,下一个线程就进行输出。从而导致有null的情况,其他情况类推。

改进版本给出不同数据,并加入同步:

学生类:

public class Student {
    String name;
    int age;
}

SetThread;

public class SetThread implements Runnable{
    private Student s;
    private int x = 0;

    //为了保证多个线程操作同一个学生,重写有参构造方法
    public SetThread(Student s) {
        this.s = s;
    }


    @Override
    public void run() {
        while(true){
            if (x%2==0){
                s.name="lyc";
                s.age=18;
            }else{
                s.name="lycc";
                s.age=19;
            }
            x++;
        }

    }
}

GetThread:

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
            System.out.println(s.name + "---" + s.age);
        }

    }
}

 

测试类

public class StudentDemo {
    public static void main(String[] args) {
        Student student = new Student();
        SetThread setThread = new SetThread(student);
        GetThread getThread = new GetThread(student);

        Thread thread = new Thread(setThread);
        Thread thread1 = new Thread(getThread);

        thread1.start();
        thread.start();
    }
}

 

 我们发现了问题

    1:同一个数据出现多次

        CPU的一点点时间片的执行权,就足够执行很多次

    2:姓名和年龄不匹配

        线程运行的随机性

怎么判断一个线程是不是安全的:

    1:是否是多线程环境  是

    2:是都有共享数据  是

    3:是否有多条语句操作共享数据  是


等待与唤醒机制:让数据能够实现依次的出现

  线程之间的通信:生产者和消费者案例

Object类中有三个方法:
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
void notify() 唤醒正在等待对象监视器的单个线程。
void notifyAll() 唤醒正在等待对象监视器的所有线程。
为什么这些方法不定义在Thread类中呢?
这些方法想要调用,必须通过锁对象进行调用,而我们知道锁对象可以是任意锁对象
所以这些方法,就必须定义在Object类中。
注意:1、不同种类的线程都要加锁。   2、不同种类的线程加的锁必须是同一把。

student:
public class Student {
    String name;
    int age;
    boolean flag = false;
}
SetThread;
public class SetThread implements Runnable {
    private Student s;
    private int x = 0;

    //为了保证多个线程操作同一个学生,重写有参构造方法
    public SetThread(Student s) {
        this.s = s;
    }


    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if (s.flag == false) {
                    if (x % 2 == 0) {
                        s.name = "lyc";
                        s.age = 18;
                    } else {
                        s.name = "lycc";
                        s.age = 19;
                    }
                    x++;
                    s.flag = true;
                    s.notify();
                } else {
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }

    }
}

 

GetThread:
 
public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if (s.flag == false) {
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name + "---" + s.age);
                s.flag = false;
                s.notify();
            }
        }

    }
}
测试类:
public class StudentDemo {
    public static void main(String[] args) {
        Student student = new Student();
        SetThread setThread = new SetThread(student);
        GetThread getThread = new GetThread(student);

        Thread thread = new Thread(setThread);
        Thread thread1 = new Thread(getThread);

        thread1.start();
        thread.start();
    }
}

优化:把数据及操作都写在了资源类中,同步方法实现

student:

 

public class Student {
    String name;
    int age;
    boolean flag = false;

    public synchronized void set(String name, int age) {
            if (this.flag == false) {
                this.name = name;
                this.age = age;
                flag = true;
                this.notify();
            } else {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    public synchronized void get(){
        if (flag == false) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(this.name + "---" + this.age);
        flag = false;
        this.notify();
    }
}

 

SetThread

public class SetThread implements Runnable {
    private Student s;
    private int x = 0;

    //为了保证多个线程操作同一个学生,重写有参构造方法
    public SetThread(Student s) {
        this.s = s;
    }


    @Override
    public void run() {
        while(true){
            if (x % 2 == 0) {
               s.set("lyc",18);
            } else {
                s.set("lycc",19);
            }
            x++;
        }
    }
}

 

GetThread:

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            s.get();
        }
    }
}

 

包子铺案例:

 

//创建包子类
public class BaoZI {
    String pi;
    String xian;

    Boolean flag = false;


}
//创建包子铺类
public class BaoZIStore extends Thread{
    private BaoZI bz;
    public BaoZIStore(BaoZI bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;//用于生产不同的包子
        while(true){
            synchronized (bz){
                if (bz.flag==false){
                    //包子铺没有包子,生产包子
                    if (count%2==0){
                        bz.pi= "薄皮";
                        bz.xian="三鲜馅";
                    }else{
                        bz.pi= "冰皮";
                        bz.xian="牛肉馅";
                    }
                    count++;
                    System.out.println("包子铺正在生产"+bz.pi+bz.xian+"包子");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    bz.flag=true;
                    bz.notify();
                    System.out.println("包子铺生产好"+bz.pi+bz.xian+"包子唤醒消费者吃包子");
                }else{
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
//创建消费者类
public class BaoZIEat extends Thread{
    private BaoZI bz;
    public BaoZIEat(BaoZI bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag==false){
                    //包子铺没有包子,消费者进入wait等待
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    System.out.println("顾客正在吃"+bz.pi+bz.xian+"包子");
                    bz.flag=false;
                    bz.notify();
                    System.out.println("顾客吃完"+bz.pi+bz.xian+"包子");
                    System.out.println("============================");
                }
            }
        }
    }
}
public class BaoZIMain {
    public static void main(String[] args) {
        BaoZI bz  = new BaoZI();

        new BaoZIStore(bz).start();
        new BaoZIEat(bz).start();
    }
}

线程的状态转换图:

 

线程组:

    Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

默认情况下,所有的线程都属于主线程组。

  public final ThreadGroup getThreadGroup()

我们也可以给线程设置分组

  Thread(ThreadGroup group, Runnable target, String name)

/*
          线程组:把多个线程组合到一起
            Java中使用ThreadGroup来表示线程组,
            它可以对一批线程进行分类管理,
            Java允许程序直接对线程组进行控制的

 */
public class ThreadGroupDemo {
    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();

        //创建线程对象
        Thread t1 = new Thread(myRunnable, "翔阳");
        Thread t2 = new Thread(myRunnable, "影山");

        //默认情况下,所有的线程都属于主线程组。
        //public final ThreadGroup getThreadGroup()
        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();

        //public final String getName()返回此线程组的名称。
        System.out.println(tg1.getName());//main
        System.out.println(tg2.getName());//main

        System.out.println(Thread.currentThread().getThreadGroup().getName());//main
        
        //Thread(ThreadGroup group, Runnable target, String name)
        //给线程分组
        //先创建一个新的线程组
        //ThreadGroup(String name)
        //构造一个新的线程组。
        ThreadGroup tg3 = new ThreadGroup("单细胞组");

        //创建线程对象
        Thread t3 = new Thread(tg3, myRunnable, "翔阳");
        Thread t4 = new Thread(tg3, myRunnable, "影山");

        System.out.println(t3.getThreadGroup().getName());//单细胞组
        System.out.println(t4.getThreadGroup().getName());//单细胞组

        //直接通过组名设置一个组都是守护线程组,组里面的线程都是守护线程
        tg3.setDaemon(true);
    }
}

 

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

线程池:

程序启动一个新的线程成本是比较高的,因为它涉及到要与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

    1、线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

  2、在JDK5之前,我们必须手动实现自己的线程池,从JDk5开始,Java内置支持线程池。

如何实现线程池的代码

      1、创建一个线程对象,控制要创建几个线程对象。

        public static ExecutorService newFixedThreadPool(int nThread)

      2、这种线程池的线程可以执行:

        可以执行Runnable对象或者Callable对象代表的线程

        做一个类实现Runnable接口。

      3、调用下面的方法即可

        Future submit(Runnable task)

        <T> future<T> submit(Callable<T> task)

      4、我就要结束,可以吗? 可以。

        shutdown()

实现Runable接口:

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

测试类:

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

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //创建一个线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);
        ExecutorService pool1= Executors.newCachedThreadPool();
        //Future<?> submit(Runnable task)
        //提交一个可运行的任务执行,并返回一个表示该任务的未来。
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        //结束线程池
        pool.shutdown();
        pool1.shutdown();
        //线程池已经被关闭,不能再重复提交运行。
//        pool.submit(myRunnable);
    }
}

实现Callable接口

import java.util.concurrent.Callable;

public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for(int i =0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        return null;
    }
}

测试类:

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

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);

        //可以执行Runnable对象或者Callable对象代表的线程
        pool.submit(new MyRunnable());

        pool.submit(new MyCallable());
        pool.shutdown();
    }
}

Callable的案例(计算1+....的总和 线程池实现)

实现Callable接口

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {
    private int number;

    public MyCallable(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
//            System.out.println(Thread.currentThread().getName()+":"+sum);
        }
//        System.out.println(Thread.currentThread().getName()+":"+sum);
        return sum;
    }

}

 

测试类:

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


public class MyCallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        Future<Integer> f1 = pool.submit(new MyCallable(50));
        Future<Integer> f2 = pool.submit(new MyCallable(100));
        Future<Integer> f3 = pool.submit(new MyCallable(200));

        int i1 = f1.get();
        int i2 = f2.get();
        int i3 = f3.get();

        System.out.println(i1);
        System.out.println(i2);
        System.out.println(i3);

        pool.shutdown();
    }

}

多线程的面试题

    a:多线程有几种实现方案,分别是哪几种?

      两种。(面试答2种

        继承Thread类

        实现Runnable接口

        扩展一种,实现Callable接口,这个要和线程池结合使用。

    b:同步有几种方式,分别是是什么?

      两种。

        同步代码块  锁是任意对象锁

        同步方法   锁是this

        同步静态方法  锁是当前类的二进制字节码文件

    c:启动一个线程是run()还是start()?它们的区别?

      start()

        run():封装了被线程执行的代码块,直接调用仅仅是一个普通方法的调用

        start():启动线程,并由JVM自动调用run()方法

    d:sleep()和wait()方法的区别

      sleep():必须指定时间,不释放锁。

      wait():可以不指定时间,也可以指定时间,但是它释放锁。

    e:为什么wait(), notify(), notifyAll()等方法都定义在Object类中?

      因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。而Object代表任意对象,所以定义在里面。

    f:线程的生命周期图(如上)

      新建 -- 就绪 -- 运行 -- 死亡

      新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡

 

线程中需要注意的问题:

  1、Thread中start()方法的功能就是创建一个新的线程,并自动调用该线程的run()方法,直接调用run()方法是不会创建一个新的线程的,直接调用相当于调用一个普通方法。

  2、执行一个线程实际就是执行该线程run方法中的代码

  3、一个Thread对象只能代表一个线程。

      一个Thread对象不能调用两次start()方法,否则会抛出java.lang.IllegalThreadStateException异常

 

posted @ 2021-10-24 22:31  艺术派大星  阅读(55)  评论(0)    收藏  举报
levels of contents