Java多线程

1.程序与进程

  1. 程序:静态的代码
  2. 进程:正在运行的程序。动态的。
  3. 线程:一个进程可以细分多个线程,可以并行(同时)执行。
  • 每个进程,独立资源分配单位;
  • 线程:调度和执行的单位,有独立的程序计数器和运行栈。
    (线程:切换环境不变;进程:切换之后环境啥的全部清空)

2. 并行和并发

  1. 并发执行:像单核cpu,同时其实只能执行一个,不同进程轮询切换,看起来好像都是同时执行的。(一段时间多个任务)
  2. 并行执行:真正的做到同时进行。(某刻时间,多个任务)

3.线程的生命周期

主要关注方法+状态

JDK中 Thread.State 的几种生命状态:

(五状态图)

  1. 新建态(创建态):声明和创建态new
  2. 就绪态:已经具备了运行的所有条件,就等着cpu的调度
  3. 运行态:运行所需的资源全部都足够了,且cpu资源现在也是自己的
  4. 阻塞态:被干预,被动进入阻塞态,让cpu终止了自己的执行
  5. 死亡(terminal):完成了所有的工作,或者被强制终止、出现异常之类的导致结束。(终态)

生命状态的转换:

  1. 创建态→就绪态:调用start(); 就绪态→运行态:获得cpu执行权;
  2. 运行态→阻塞态:被剥夺资源:sleep(long time);join()方法;等待同步锁;wait()状态、suspend();(阻塞只能到就绪)
  3. 阻塞态→就绪态:sleep()结束;join()结束;获取了同步锁;
  4. 运行态→就绪态:失去cpu的执行权(时间片到、yield();)
  5. 运行态→死亡态:run方法执行结束;stop调用线程;异常、error且未处理;

4.※线程的创建和使用:(4种方式)

  • thread类的方式 、 runnable接口的方式、新增的callable方式、线程池的方式。

  • 单核cpu,先后完成任务比使用多个线程完成更快。
    解释:单核的多线程在切换的时候会用很多时间。

  • 多线程:提高用户体验。图形化界面更有意义。多模块,方便修改等。

Thread类的有关方法:

  1. start();启动当前线程,再调用当前线程的run()方法(因此是不可以直接启动run()方法的,必须是start()方法。
  2. run();通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
  3. currentThread();静态方法,返回执行当前代码的线程 (默认thread-0)
  4. getName();获取当前线程的名字
  5. setName();设置当前线程的名字
public class javaMethodTest {
  public static void main(String[] args) {            //主线程
      HelloThread h1 = new HelloThread();
      h1.setName("线程一");		//子线程的命名
      h1.start();
      Thread.currentThread().setName("主线程");          //给主线程命名(当前的线程)
      for (int i = 0; i < 100; i++) {
          if(i%2!=0){
              System.out.println(Thread.currentThread().getName() + ":" + i);
          }
      }
  }
}
class HelloThread extends Thread{
  @Override
  public void run() {
      for (int i = 0; i < 100; i++) {
          if(i%2==0){
              System.out.println(Thread.currentThread().getName() + ":" + i);
          }
       }
   }
}

构造器命名:

public class javaMethodTest {
    public static void main(String[] args) {            //主线程
        HelloThread h1 = new HelloThread("Thread:1");		//写入要改的参数(名字)
  
        h1.start();

        Thread.currentThread().setName("主线程");          //给主线程命名
        for (int i = 0; i < 100; i++) {
            if(i%2!=0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
class HelloThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            }
        }
        public HelloThread(String name){				//构造器传参改名
            this.super(name);		//这里的this相当于是Thread.currentThread()这里指的是当前的线程。
        }
}
  1. yield();释放当前cpu的执行权
  2. join();用thread调用,本身会抛出异常,try- catch一下:优先处理的一个线程(在线程a中调用线程b的join(),此时a阻塞,b完全执行完a才不阻塞)
  3. stop();已过时了,强制结束当前线程
  4. sleep(long millitime);单位是ms,抛出异常:try-catch,这里也不可以用抛出异常的方法(父类没有抛出异常的时候,子类不可以抛)(让当前线程在指定的时间内阻塞)
  5. ***.isAlive(); 判断当前线程是否存活。

线程通信方法:wait();notify();notifyAll();(不是Thread里面的方法)

线程的优先级调度:(OS)

Java调度方法:

  1. 相同优先级先进先出队列(先来先服务),时间片轮转策略
  2. 对高优先级,使用优先调度抢占式策略
    MAX_PRIORITY=10;(最高级)
    MIN_PRIORITY=1;
    NORM_PRIORITY=5(默认优先级都是5)

获取当前线程的优先级:
.getPriority();默认的(未设置过的为5)
设置新的优先级:
setPriority();

 Thread.currentThread().setPriority(Thread.MIN_PRIORITY);   
♥ 注意: 设置之后,得到的并不是哪个优先级高就先把高的执行完,实际上还是交互性的。只是从概率上来讲,高概率的情况下会有高优先级优先执行。

执行结果如下:

5:0
1:1
5:2
1:3
5:4
1:5
.....
  • 何时需要多线程?(守护线程+用户(主)线程)
		多任务同时执行;
		存在等待(资源)的任务;
		后台运行。

多线程的创建:

(一条路径:单线程)
多线程:调用Thread类

1. 方法一:继承thread类的方式:

step1:声明(创建)thread类的子类;
step2:重写thread类的run();
step3:创建thread类的子类对象;
step4:通过此对象调用start()。

Thread extends Thread{   //1.创建thread子类继承Thread(子线程)

    //2.重写Thread类的run()方法。run+enter.
    @Override
    public void run() {
        //该线程需要做到的事情写在run()方法体中;
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest {       //(主线程)

    public static void main(String[] args) {
	// 3.创建Thread类的子类对象。new 上面的新线程,alt+enter
        MyThread t1 = new MyThread();

        // 4.通过此对象调用start()方法
        t1.start ();
        for (int i = 0; i <100 ; i++) {
            if(i%2 != 0){
                System.out.println("********");
            }
        }

主线程和子线程交互进行:(与不同计算机之间的处理频率有关)执行结果如下:

********
********
0
********
********
2
4
6
********
......

2.Thread匿名子类

/**
* 两个线程遍历不一样的,一个遍历100内的偶数,一个遍历100内的奇数
*/
public class ThreadDemo {
 public static void main(String[] args) {
     MyThread1 t1 = new MyThread1();
     MyThread2 t2 = new MyThread2();			//3. 创建两个线程的对象;

     t1.start();
     t2.start();			//4. 在主线程里面调用子类对象start()一下。
 }
}

class MyThread1 extends Thread{			//1.创建不同的继承Thread类的两个方法1(线程1)
 @Override
 public void run() {			//2.重写run();方法
     for (int i = 0; i < 100; i++) {
         if(i%2==0){
             System.out.println(Thread.currentThread().getName()+":"+i);
         }
     }
 }
}

class MyThread2 extends Thread{			//1. 创建两个不同的继承Thread类的方法2(线程2)
 @Override
 public void run() {
     for (int i = 0; i < 100; i++) {
         if(i%2!=0){
             System.out.println(Thread.currentThread().getName()+":"+i);
         }
     }
 }
}

得出交互性的结果:

Thread-1:1
Thread-0:0
Thread-1:3
Thread-0:2
Thread-1:5
Thread-0:4
Thread-1:7
Thread-0:6
......

(简便写法):创建Thread类的匿名子类:

public class ThreadDemo {
    public static void main(String[] args) {
       new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 100; i++) {
                  if(i%2==0){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                  }
               }
           }
       }.start();

       new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 100; i++) {
                   if(i%2!=0){
                       System.out.println(Thread.currentThread().getName()+":"+i);
                   }
               }
           }
       }.start();
    }
}

3. 方法二:实现Runnable接口:

1. 创建一个实现了Runnable接口的类
 ```javaclass MThread implements Runnable{   } ```
 2. 实现类去实现runnable中的抽象方法:run();
 3. 创建实现类的对象;new***
 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5. 通过Thread类的对象调用start();
class MThread implements Runnable{    //1.创建的一个runnable接口;会有提示错误,alt+enter到2.

    @Override               //2.实现类去实现runnable中的抽象方法:run();
    public void run() {
    
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+ i);
            }
        }
    }
}

public class ThreadTest2 {
    public static void main(String[] args) {
        //3.创建实现类的对象
        MThread mThread = new MThread();
        // 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        //5. 通过thread调用start();启动线程、调用当前线程的run(),调用了runnable类型的target
        t1.start();

        Thread t2 = new Thread(mThread);
        t2.setName("线程2");
        t2.start();
    }
}
区别:继承类有一定的局限性(共享声明static才行),而且实现方法天然地实现了共享,因此实现方式更优;如果创建的多个线程需要有共享数据,用实现的方式也可以天然地实现共享。

联系:thread类本身也实现了runnable方法;都需要重写run()方法,将要做的逻辑声明在run()方法中。

总结:
 	1. 实现的方式,没有类的单继承性的局限性
	2.实现的方式更适合处理多个线程有共享数据的情况。

4.新增方式一(方法三):Callable接口

  • 与Runnable相比的强大的有:
  1. runnable重写的是run()方法,无返回值;Callable重写的是 call()方法,可以有返回值。
  2. runnable只能try-catch异常,callable可以抛出throw异常,被外面的捕获,获取异常的信息。
  3. callable支持泛型的返回值。<>里面决定类型。
  4. 需要借助future task类,比如获取返回结果。(future task本身就实现了runnable接口)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//1. 创建一个实现callable的实现类
class NumThread implements Callable{    //<>泛型
// 2. 重写call()方法,需要在执行的声明在call()方法中。可以抛出异常
    @Override
    public Object call() throws Exception {
            int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if(i%2==0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class ThreadNew {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();          //3. 建一个callable接口实现类的对象;
        //4. 将callable接口实现类的对象作为参数传递到Future Task的构造器中,创建Future Task的对象
        FutureTask futureTask = new FutureTask(numThread);
        // 5. 将future task的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。
        new Thread(futureTask).start();
        
        //get()方法
        try {       //6. 需要返回值才要的get()方法
            //get方法的返回值,即为future task构造器参数callable实现类重写call的返回值(不需要返回值时,可以不用get来获取返回值)
            Object sum = futureTask.get();
            System.out.println("打印的总额为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

5. 新增方法二(方法四):使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。===于是可以提前造好多个线程,使用时直接获取,用完放回,不销毁。
优点:可以避免频繁创建、销毁,还可以实现重复利用。

  1. 可以降低相应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用,也不要每次创建一次)
  3. 便于线程管理。(提前规定一下:核心池大小corePoolSize、最大线程数maximumPoolSize、无连接多少时间会释放keepaLiveTime…)

使用线程池方式:

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

class NumberThread implements Runnable{

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10); //造了一个10个线程的线程池,具体到做什么还是需要自己再做

        //2. 提供一个实现runnable或者callable接口的类的对象
        service.execute(new NumberThread());             //适合用于runnable
        service.execute(new NumberThread1());             //适合用于runnable
       //  service.submit();           //适合适用于callable
      
        //3. 关闭
        service.shutdown();    //关闭线程。
        service.shutdown();    //关闭线程。
    }
}

实现线程(属性)管理设置:

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

class NumberThread implements Runnable{

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10); //造了一个10个线程的线程池,具体到做什么还是需要自己再做
        //上面是接口,接口中的常量不用设置。下面进行强制转换,得以继承,使用子类的特有方法
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;

        //属性设置
        System.out.println(service.getClass());
        service1.setCorePoolSize(15);
        //....

        //2. 提供一个实现runnable或者callable接口的类的对象
        service.execute(new NumberThread());             //适合用于runnable
        service.execute(new NumberThread1());             //适合用于runnable
       //  service.submit();           //适合适用于callable

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

总结:创建多线程的实现方式有4种。

5.※线程的同步:解决线程安全的问题:3种方式

共享的资源同步使用时会产生的安全问题:

一个实际问题:

买票的过程中出现重复票,或者错票的问题-1

  • 问题的原因:还没操作完成时,其他线程参与进来,进行同样的操作,就混了。
  • 解决方法:当一个线程在操作时,其他线程不可以参与进来,直到他完成操作时,其他的ticket才能参与进来。即使a出现了阻塞,也不可以被改变。(上锁)
  • 代码解决方法:同步机制解决

1. 方式一:同步代码块

① runnable接口实现:

	synchronized(同步监视器){
	// 需要被同步的代码(不能包多了,也不能包少了)
	
 		}
说明:1.操作共享数据的代码是什么,即为需要被同步的代码
	  2.有无共享数据:多个线程共同操作的变量(数据)
      3.同步监视器:锁。	要求多个线程必须要共用一把锁。(随便是什么锁,但是必须同一个)
      4.操作同步代码时,只能有一个线程参与,相当于单线程的过程了,效率相对降低了。
class Window1 implements Runnable{
    private int ticket = 100;
    Object obj = new Object();      //任意一个类:谁的对象无所谓,但是必须是共用的一个

    @Override
    public void run() {
        while (true){
            synchronized(obj){
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);   //这里阻塞,可能正好大家都被阻塞了。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class safeProblem {
    public static void main(String[] args) {

        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

    }
}

更简便的方法:synchronized(this){ }

② 使用同步代码块,解决继承Thread类的方式的线程安全问题:
继承方式下:static共享数据才可以。

继承Thread的方式:

class Window1 extends Thread{ }

添加静态声明:

static Object obj = new Object(); 

创建多个对象:

        Window1 t1 = new Window1();
        Window1 t2 = new Window1();
        Window1 t3 = new Window1();

所以在这个继承Thread的方式下,创建了多个对象,不能用this(多个不同的锁)。可以写成synchronized(window1.class){ }
(类也是对象:且类只会加载一次)

2. 方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的———同步方法

① 使用同步方法来解决实现runnable接口的线程安全问题:
(把需要synchronized包起来的代码块单独写一个类)

class Window1 implements Runnable{
    private int ticket = 100;       //共享数据

    @Override
    public void run() {
        while (true){
        show();
        }
    }
    private synchronized void show(){			//直接声明一下synchronized即可     (在这个show方法中的同步监视器就是this)
        if (ticket > 0) {
            try {
                Thread.sleep(100);   
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+ ":卖票,还剩下" + ticket);
            ticket--;
        }
        }
    }

②使用同步方法解决继承Thread线程安全问题

class Window1 extends Thread{
    private static int ticket = 100;       //共享数据

    @Override
    public void run() {
        while (true){
        show();
        }
    }
    private static synchronized void show(){
    //这里的同步监视器修改后从t1、t2、t3变成了当前的类
        if (ticket > 0) {
            try {
                Thread.sleep(100);   //这里阻塞,可能正好大家都被阻塞了。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+ ":卖票,还剩下" + ticket);			//静态调用
            ticket--;
        }
        }
    }

关于同步方法的总结:

  1. 仍然涉及到同步监视器,但是不需要显示声明。
  2. 非静态的同步方法,同步监视器为this(runnable接口)
    静态的同步方法,同步监视器为当前类(Thread继承类)。

方式三:同步死锁问题(lock锁)

死锁:不同的线程分别占用对方的资源不放弃,都在等对方先放弃,所有的线程都处于阻塞状态,无法继续。

说明:死锁之后不会出现异常,不会报错,只是都处于阻塞态。使用同步时,要避免死锁。

lock.lock();+lock.unlock();中间的锁上。

  • Q & A :
    1. synchronized与lock方法的相同点:都可以用来解决线程的安全问题
    2. 不同点:lock是手动的上锁lock和解锁unlock;synchronized是执行完之后自动的释放同步监视器。
    3. 选择:大部分都选的是synchronized,但是lock是jdk后面的版本新增的,而且手动的lock更加灵活,所以倾向优先用lock
  • 建议的顺序:lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

同步线程的练习:

Q:银行有一个账户;两个储户分别向同一个账户存3000元,每次存1000元,存三次。每次打印账户余额。
存在的什么安全问题?解决方案?

分析:
1. 是否是多线程?是,两个储户线程
 2. 是多线程一定存在线程安全问题吗?--取决于是否有共享数据
有共享数据,账户(或者账户余额)
 3. 存在安全问题吗?--两个线程共享操作共享数据。--存在。
 4. 解决多线程安全问题:同步机制--三种方法
class Account{
private double balance;

    public Account(double balance) {
        this.balance = balance;
    }
    //存钱的方法
    public synchronized void deposit(double amt){
        if(amt > 0){
            balance += amt;

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

            System.out.println(Thread.currentThread().getName()+"存钱成功,当前余额是:" + balance);
        }
    }

}
class Customer extends Thread{
    private Account acct;

    public Customer(Account acct){
        this.acct = acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {

            acct.deposit(1000);
        }
    }
}
public class AccountTest {
    public static void main(String[] args) {
        Account acct = new Account(0);

        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);

        c1.setName("甲");
        c2.setName("乙");

        c1.start();
        c2.start();
    }
}

6.线程的通信:

三个方法:

  1. wait()方法:一旦执行此方法,当前线程进入阻塞态,并释放同步监视器。

  2. notify()方法:一旦执行此方法,就会唤醒一个wait的线程,如果有多个线程被wait,就唤醒优先级高的,优先级相同的时候,随机唤醒一个。

  3. notifyAll()方法:一旦被执行,唤醒所有的wait。

说明:
	1. 这三个方法都必须是使用在同步代码块或者同步方法中,调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常。(省略了this或者此类来调用的)
	2. 这三个方法都是定义在java.lang.object类中的

sleep()和wait()方法的异同:

  • 相同点:一旦执行,都会使当前的线程进入阻塞态;
  • 不同点:
  1. 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait();
  2. 调用的要求不一样,sleep()可以在任何场景下调用,wait()只能用在同步代码块或者同步方法由同步监视器中
  3. 是否释放同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放同步监视器,wait()会释放同步监视器。(wait有)

多线程问题:①生产者-②消费者问题

  1. 生产者productor:生产产品,将产品交给店员clerk(clerk最多只能有20个产品)
  2. 店员clerk:生产者生产了20个就会试图让生产者停止生产,没产品了让生产者生产一下,让消费者等一下
  3. 消费者custome:消费产品
  • 可能的问题:
/**
 * 分析:
 * 1. 是否是多线程的?--是,生产者、消费者线程
 * 2. 是否有共享数据?--有,店员(或者产品、产品的数据—)
 * 3. 如何解决线程的安全问题?--同步机制,有三种方法
 * 4.是否涉及到线程的通信?--是,sleep、wait、notify、、、
 */
class Clerk{   //①  共享数据类

    private int productCount = 0;       //⑧ 共享数据变量:仅体现产品数量上的变化
//⑦ 第一个方法:生产产品
    public synchronized void produceProduct() {      //⑨ 同步监视器clerk

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

            notify();       //最后,唤醒另一个线程。

        }else{
            try {
                wait();         //⑩ wait()没有notify()的情况下:消费者一直都是0的状态,于是不消费。+try-catch包住
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    
//⑦ 第二个方法:消费产品
    public synchronized void eatProduct() {         //⑨ 同步监视器clerk
        if(productCount>0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();           //最后,唤醒另一个线程

        }else {

            try {
                wait();     //⑩ wait()没有notify()的情况下:消费者一直都是0的状态,于是不消费。+try-catch包住
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer extends Thread{      //①  多线程--生产者继承Thread线程
    private Clerk clerk;         //② 共享变量:生产者和消费者公用的变量clerk+构造器构造方法

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

    @Override               //③ 重写run()方法
    public void run() {

        System.out.println("生产者" + Thread.currentThread().getName() + ":开始生产产品");
    while(true){

        try {
            Thread.sleep(10);               // ④ 写得慢点 thread.sleep(ms);+try-catch包一下
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        clerk.produceProduct();              //⑥ 共用类调用生产者方法;  生产者方法不存在,alt+enter快捷键生成一个在clerk类中。
    }
    }
}
class Eat extends Thread{           //① 多线程--消费者继承Thread类
    private Clerk clerk;            //② 共享变量:生产者消费者共用的变量clerk+构造器构造

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

    @Override                   //③ 重写run()方法
    public void run() {

        System.out.println("消费者" + Thread.currentThread().getName() + ":开始消费产品");
while(true){

    try {
        Thread.sleep(20);                    // ④ 写得慢点 thread.sleep(ms);+try-catch包一下
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    clerk.eatProduct();                     //⑥ 共用类调用生产者方法;  生产者方法不存在,alt+enter快捷键生成一个在clerk类中。
}
    }
}

public class ProductTest {          //① 主线程
    public static void main(String[] args) {
        Clerk clerk = new Clerk();          //新建公共变量用来调用
        Producer p1 = new Producer(clerk);      //调用
        p1.setName("生产者");              //更名

        Eat e1 = new Eat(clerk);
        e1.setName("消费者1");
        Eat e2 = new Eat(clerk);
        e2.setName("消费者2");

        p1.start();             //调用+启动
        e1.start();
        e2.start();

    }
}

同步和异步:通常用来形容一次方法调用。

  • 同步方法调用:一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
  • 异步方法调用:更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作。
    同步类似于单线程;异步类似于两个一起同时进行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值