Java学习笔记 --多线程

一、程序、进程、线程

(一)相关概念
  • 程序 Program:完成特定任务,用某种语言编写的一组指令的集合,指一段静态的代码,静态对象。
  • 进程 Process:一个程序的执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生、存在和消亡的过程(生命周期
    • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
  • 线程 Thread:进程可以进一步细化为线程,是一个程序内部的一条执行路径
    • 若一个进程同一时间并行执行多个线程,就是支持多线程的
    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
    • 一个进程中的多个线程共享相同的内存单元/内存地址空间 -> 他们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更加简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患
    • 虚拟机栈&程序计数器每个线程中拥有一套
      方法区&堆一个进程拥有一套,多个线程共享一个进程中的方法区和堆
  • 线程可以细化为多个进程
    每个线程拥有自己独立的虚拟机栈和程序计数器
    多个线程,共享同一个进程中的堆和方法区
    在这里插入图片描述
(二)单核CPU和多核CPU
  • 单核CPU:其实是一种假的多线程
  • 多核CPU:同时执行, 发挥多线程的效率,现在的服务器都是多线程的
  • 一个java应用程序java.exe至少有三个线程:main()主线程、gc()l垃圾回收线程、异常处理线程
    如果发生异常,会影响主线程
(三)并行与并发
  • 并行:多个CPU同时执行多个任务。Eg. 多个人同时做不同的事
  • 并发:一个CPU“同时”(快速切换)执行多个任务。Eg. 秒杀
(四)使用多线程的优点
  • 提高应用程序的相应
  • 提高计算机系统CPU的利用率
  • 改变程序结构,将长且复杂的进程分为多个线程,独立运行,利于理解和修改
(五)何时需要多线程
  • 程序徐奥同时执行两个或多个任务
  • 程序需要实现一些需要等待的任务时,Eg. 用户输入、文件读写、网络操作…
  • 需要一些后台运行的程序时

二、线程的创建和使用

(一)线程的创建
方式一:继承Thread类 or 创建Thread类的匿名子类
  1. 继承Thread类
  2. 重写Thread类中的run()方法,将此线程执行的操作声明在run()中
  3. 创建Thread类子类的对象
  4. 通过此对象调用start()
  • 不可以让已经执行start()的线程再去执行start(), 会报异常java.lang.IllegalThreadStateException,若需开启一个新线程,需要重新new一个对象
  • 启动一个线程, 必须调用start()方法, 无法直接通过调用run()方法启动一个新线程
继承Thread类
// Step1:继承Thread类
class MyThread extends Thread {
	// Step2:重写Thread类中的run() --> 将此线程执行的操作声明在run()中
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 100; i++) {
			if (i % 2 == 0) {
				System.out.println(Thread.currentThread().getName() + i);
			}
		}
	}
}

public class ThreadTest1 {
	public static void main(String[] args) {
		// Step3:创建Thread类的子类对象
		MyThread t1 = new MyThread();
		// Step4:通过此对象调用start()
		t1.start();
		
		t1.start();// 不可以让已经执行start()的线程再去执行start(), 会报异常java.lang.IllegalThreadStateException
		           // 若需要开启一个新线程,需要重新new一个对象
		t1.run();// 无法直接通过调用run()方法启动一个新线程
	}
}
创建Thread类的匿名子类
public class ThreadTest {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                super.run();
                System.out.println("innerrun()");
            }
        }.start();
    }
}
方式二:实现Runnable接口
  1. 创建一个实现了Runnable接口的类
  2. 在实现类中实现Runnable接口的run()方法
  3. 创建实现类对象
  4. 创建Thread类的对象, 将实现类对象作为参数传入Thread类的构造器中
  5. 通过Thread类的对象调用start()方法
public class ThreadTest1 {
    public static void main(String[] args) {
        //Step3: 创建实现类的对象
        MyThread1 myThread = new MyThread1();
        // Step4: 将此对象作为参数传入Thread类的构造器中, 创建Thread类的对象
        Thread thread1 = new Thread(myThread);
        // Step5: 通过Thread类的对象调用start()
        thread1.start();

        // 创建多个线程
        Thread thread2 = new Thread(myThread);
        thread2.start();

        Thread thread3 = new Thread(myThread);
        thread3.start();
    }
}

// Step1: 创建一个实现了Runnable接口的类
class MyThread1 implements Runnable {

    // Step2: 实现类实现Runnable中的run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}
  • 对比两种创建方式: 开发中优先选择实现Runnable接口的方式
    • 继承Thread类方法创建线程, 该类无法再此继承其他类, 具有局限性
    • 实现Runbbale接口的方式更适合处理多个线程共享数据的情况
    • 相同点:
      • 两种方式都需要重写run(), 将线程需要执行的逻辑声明在run()中
      • 两种方式都需要调用Thread类中的start()方法启动线程
      • public class Thread implements Runnable

(二)Thread类的相关方法
  1. void start(): 启动当前线程, 并执行对象的run()方法
  2. run(): 线程在被调度时执行的操作写在此方法中, 通常需要重写Thread类中的此方法
  3. String getName(): 返回线程的名字
  4. void setName(String name) :设置当前线程的名字
  5. static Thread currentThread(): 返回执行当前代码的线程
public class ThreadMethodTest {
    public static void main(String[] args) {
        //1. 通过构造器给当前对象调用线程命名
        MethodTest1 test1 = new MethodTest1("ThreadNewName");
        //2. 给当前对象调用线程命名
        test1.setName("Thread1-");
        test1.start();

        //给主线程命名
        Thread.currentThread().setName("MainThread-");
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}

class MethodTest1 extends Thread {
    public MethodTest1(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}
  1. static void yield(): 线程让步,释放当前cpu执行权
    –> 暂停当前执行的线程, 把执行机会让给优先级相同或更高的线程
    –> 若队列中没有同优先级的线程, 忽略此方法
class MethodTest6 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if (i % 2 == 0) {
				System.out.println(Thread.currentThread().getName() + i);
			}

			if (i % 20 == 0) {
				yield(); //this.yiele():
			}
		}
	}
}
  1. join(): 在线程A中调用线程B的join()方法时, 线程A将被阻塞, 直到调线程B完全执行完以后,线程A才结束阻塞状态,继续执行
    –> 低优先级的线程也可以获得执行
public class ThreadMethodTest {
	public static void main(String[] args) {
		MethodTest1 test1 = new MethodTest1();
		test1.start();

		for (int i = 0; i < 100; i++) {
			if (i % 2 == 0) {
				System.out.println(Thread.currentThread().getName() + i);
			}

			if (i == 20) {
				try {
					test1.join(); // 在主线程中调用另一个线程对象的join()方法
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

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

图片描述

  1. stop(): 强制线程生命结束, 不推荐使用 -> Deprecated 已过时
  2. static void sleep(long millis): (指定时间: ms) 让当前线程“睡眠”指定millitime毫秒,在指定的millitime毫秒内,当前线程时阻塞状态,阻塞时间结束后重新排队
    –> 抛出InterruptedException异常
class MethodTest1 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if (i % 2 == 0) {
				try {
					sleep(1000);
					System.out.println("-------sleeping");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + i);
			}
		}
	}
}
  • boolean isAlive(): 返回boolean, 判断线程是否还活着
public class ThreadMethodTest {
	public static void main(String[] args) {
		MethodTest1 test1 = new MethodTest1();
		test1.start();

		for (int i = 0; i < 100; i++) {
			try {
				test1.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(test1.isAlive()); // 判断test1对象线程是否仍然存活  --> fa;se0

	}
}
(三)线程的调度
  • 调度策略
    • 时间片
    • 抢占式:高优先级的线程抢占CPU
  • 调度方法
    • 同优先级线程组成,先进先出,使用时间片策略
    • 对高优先级,使用优先调度的抢占式策略
  • 线程的优先级等级
    • MAX_PRIORITY: 10
    • MIN_PRIORITY: 1
    • NORM_PRIORITY: 5 --> 默认优先级
  • 涉及的方法
    • getPriority() : 返回线程的优先级
    • setPriority() : 改变线程的优先级
  • 说明
    • 线程创建时,继承父线程的优先级
    • 低优先级只是获得调度的概率低,并非一定在高优先级线程之后才被调用
(四)线程的分类
  • 用户线程: 主线程就是用户线程, 用户线程执行结束时, 守护线程就会执行结束
  • 守护线程: 服务用户线程, 通过在start()方法前调用thread.setDaemon(true) 可以把一个用户线程变成一个守护线程
  • Java垃圾回收就是守护线程
  • 若JVM中都是守护线程,当前JVM将退出

三、线程的生命周期

  • 新建: 当Thread类或其子类的对象被声明并创建时, 新生的线程对象处于新建状态
  • 就绪: 处于新建状态的线程被start()后, 将进入线程队列等待CPU时间片, 此时他已经具备了运行的条件, 只是没有分配到CPU资源
  • 运行: 当就绪的线程被调度并获得CPU资源时, 便进入运行状态, run()方法定义了线程的操作和功能
  • 阻塞: 在某种特殊情况下, 被人为挂起或执行输入输出操作时, 让出CPU并临时终止自己的执行, 进入阻塞状态
  • 死亡: 线程完成了它的全部工作, 或线程被提前强制终止, 或出现异常导致结束
    在这里插入图片描述

四、线程的安全问题

  • Java中通过同步机制解决线程安全问题
    • 同步代码块
    • 同步方法
(一)同步代码块
  • 操作共享数据的代码,即需被同步的代码
    • 共享数据:多个线程共同操作的变量
    • 同步监视器:( synchronized ) 任何一个类的对象,都可被称为锁
    • 多个线程必须共用同一把锁
  • 使用同步代码块,解决了线程的安全问题,操作同步代码时,只有一个线程参与,其他线程等待,相当于是一个单线程 --> 效率低
  synchronized (同步监视器) {
  	// 需要被同步的代码
  }
1. extends Thread --> public static Object obj = new Object();
public class ThreadTest4 {
  public static void main(String[] args) {
  	MyThread4 thread1 = new MyThread4();
  	MyThread4 thread2 = new MyThread4();
  	MyThread4 thread3 = new MyThread4();

  	thread1.setName("Thread1- ");
  	thread2.setName("Thread2- ");
  	thread3.setName("Thread3- ");

  	thread1.start();
  	thread2.start();
  	thread3.start();
  }
}

class MyThread4 extends Thread {

  public static int ticket = 100;
  public static Object obj = new Object();

  @Override
  public void run() {
  	while (true) {
  		synchronized (obj) {
  			if (ticket > 0) {
  				System.out.println(Thread.currentThread().getName() + ticket);
  				ticket--;
  			} else
  				break;
  		}
  	}
  }
}
2. implements Runnable --> synchronized (MyThread3.class)
public class ThreadTest3 {
	public static void main(String[] args) {
		MyThread3 myThread = new MyThread3();
		Thread thread1 = new Thread(myThread);
		Thread thread2 = new Thread(myThread);
		Thread thread3 = new Thread(myThread);

		thread1.setName("Thread1- ");
		thread2.setName("Thread2- ");
		thread3.setName("Thread3- ");

		thread1.start();
		thread2.start();
		thread3.start();
	}
}

class MyThread3 implements Runnable {
	public int ticket = 100;

	@Override
	public void run() {
		while (true) {
			synchronized (MyThread3.class) {
				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName() + ticket);
					ticket--;
				} else
					break;
			}
		}
	}
}
3. implements Runnable --> synchronized (this)
class MyThread3 implements Runnable {
	public int ticket = 100;

	@Override
	public void run() {
		while (true) {
			synchronized (this) { // 唯一的Runnable对象
				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName() + ticket);
					ticket--;
				} else
					break;
			}
		}
	}
}
(二)同步方法
  • 当操作共享数据的代码完成的声明在一个方法中,我们可以将这个方法声明为同步
  • 同步方法仍然涉及同步监视器,只是不需要显示声明
    • 当同步方法是非static时,同步监视器为this
    • 当同步方法是static时,同步监视器是本身(类)
1. implements Runnable
public class ThreadTest5 {
	public static void main(String[] args) {
		MyThread5 myThread = new MyThread5();
		Thread thread1 = new Thread(myThread);
		Thread thread2 = new Thread(myThread);
		Thread thread3 = new Thread(myThread);

		thread1.setName("Thread1- ");
		thread2.setName("Thread2- ");
		thread3.setName("Thread3- ");

		thread1.start();
		thread2.start();
		thread3.start();
	}
}

class MyThread5 implements Runnable {
	public int ticket = 100;

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

	private synchronized void show() {
	// 同步监视器 this
		if (ticket > 0) {
			System.out.println(Thread.currentThread().getName() + ticket);
			ticket--;
		}
	}
}
2. extends Thread
public class ThreadTest6 {
	public static void main(String[] args) {
		MyThread6 thread1 = new MyThread6();
		MyThread6 thread2 = new MyThread6();
		MyThread6 thread3 = new MyThread6();

		thread1.setName("Thread1- ");
		thread2.setName("Thread2- ");
		thread3.setName("Thread3- ");

		thread1.start();
		thread2.start();
		thread3.start();
	}
}

class MyThread6 extends Thread {

	public static int ticket = 100;

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

	private static synchronized void show() { 
	// 没有static则是创建三个不同对象
	// 同步监视器 MyThread6.class
		if (ticket > 0) {
			System.out.println(Thread.currentThread().getName() + ticket);
			ticket--;
		}
	}
}

五、线程安全的单例模式之 懒汉式

class Bank {
	private Bank() {}

	private static Bank instance = null;

	public static Bank getInstance() {
		if (instance == null) {
			synchronized (Bank.class) {
				if (instance == null) {
					instance = new Bank();
				}
			}
		}
		return instance;
	}
}

六、线程的死锁问题

  • 死锁:不同的线程分别占用对方需要的同步资源不释放,都在等待对方释放自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程处于阻塞状态,无法继续
public class ThreadTest7 {
	public static void main(String[] args) {
		StringBuffer s1 = new StringBuffer();
		StringBuffer s2 = new StringBuffer();

		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (s1) {
					s1.append("a");
					s2.append("1");
				}

				try {
					Thread.sleep(100);
				} 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() {
			@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();
	}
}
ab
12
abcd
1234
public class DeadLockTest2 implements Runnable {

	A a = new A();
	B b = new B();

	public void init() {
		Thread.currentThread().setName("MainThread");
		a.foo(b);
		System.out.println("after MainThread...");
	}

	@Override
	public void run() {
		Thread.currentThread().setName("SubThread");
		b.bar(a);
		System.out.println("in SubThread...");
	}

	public static void main(String[] args) {
		DeadLockTest2 d2 = new DeadLockTest2();
		new Thread(d2).start();
		d2.init();
	}

}

class A {
	public synchronized void foo(B b) {
		System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入了A类的foo方法");

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

		System.out.println("当前线程:" + Thread.currentThread().getName() + ",准备调用B类的last方法");
		b.last();
	}

	public synchronized void last() {
		System.out.println("in A last");
	}

}

class B {
	public synchronized void bar(A a) {
		System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入了B类的bar方法");

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

		System.out.println("当前线程:" + Thread.currentThread().getName() + ",准备调用B类的foo方法");
	}

	public synchronized void last() {
		System.out.println("in B last");
	}
}

七、Lock锁

  • JDK5.0起, Java通过显式定义同步锁来实现对象同步
  • java.util.concurrent.locks.Lock接口, 是控制多个线程对共享资源进行访问的工具, 锁提供了对共享资源的独占访问, 每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应该先获得Lock对象
  • ReentrantLock类实现了Lock, 可以显示加锁,释放锁
  • Lock锁和Synchronized的区别
    • Synchroinzed机制在执行完相应的同步代码以后, 自动释放同步监视器 <隐式锁>
    • Lock需要手动启动同步( lock() ), 同时结束同步也需要手动实现( unlock() ) <显式锁>
    • Lock只有代码块锁, Synchronized有代码块锁和方法锁
    • 使用Lock锁, JVM将话费更少的时间调度线程, 性能更好, 并且具有更好的扩展性(提供更多子类)
  • 使用顺序
    Lock -> 同步代码块(已经进入了方法体, 分配了相应资源) -> 同步方法(在方法体之外)
  • 具体使用:
    -1. 实例化ReentrantLock
    -2. 在try{}中调用锁定lock()方法
    -3. 在finally{}中调用解锁unlock()方法
public class LockTest1 {
    public static void main(String[] args) {
        Window window = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("thread1 - ");
        thread2.setName("thread2 - ");
        thread3.setName("thread3 - ");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window implements Runnable { // 如果是extends Thread 

    private int ticket = 100;
    // Step1 实例化ReentrantLock
    // 如果是extends Thread, 需要将lock声明为static的
    private ReentrantLock lock = new ReentrantLock(false); //boolean fair: 谁先来的谁先执行

    @Override
    public void run() {
        while (true) {
            try {
                // Step2 调用锁定lock()方法
                lock.lock();
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "sales :" + ticket);
                    ticket--;
                } else
                    break;
            } finally {
                // Step3 调用解锁unlock()方法
                lock.unlock();
            }
        }
    }
}
练习 – 存钱
public class LockTest2 {
    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();
    }
}

class Account {
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    public synchronized void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            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);
        }
    }
}

八、线程通信

  • 线程通信:两个线程交替打印100以内的数
    • wait(): 一旦进入此方法,当前线程进入阻塞状态,并释放锁(同步监视器)
    • notify(): 一旦进入此方法,则唤醒被wait()的一个线程,如果有多个线程被wait(),则唤醒优先级高的
    • notifyAll(): 一旦进入此方法,则唤醒被wait()的所有线程
    • 注意:
      • wait()、notify()、notifyAll() 必须使用在同步代码块或同步方法中
      • 三个方法前省略的是this.
      • 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常IllegalMonitorStateException
	public static void main(String[] args) {
		Number number = new Number();
		Thread thread1 = new Thread(number);
		Thread thread2 = new Thread(number);

		thread1.setName("Thread1");
		thread2.setName("Thread2");

		thread1.start();
		thread2.start();
	}
}

class Number implements Runnable {

	private int num = 1;

	@Override
	public void run() {
		while (true) {
			synchronized (this) {
				notify(); // notify()只唤醒一个线程,notifyAll()唤醒所有wait()的线程

				if (num <= 100) {
					System.out.println(Thread.currentThread().getName() + ":" + num);
					num++;

					try {
						// 使得调用如下wati()的线程进入阻塞状态,并释放锁
						wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

				} else
					break;
			}
		}
	}
}
  • 注意:
    • wait()、notify()、notifyAll() 必须使用在同步代码块或同步方法中
    • 三个方法前省略的是this.
    • 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常IllegalMonitorStateException
    • 三个方法定义在java.lang.Object中
class Number implements Runnable {

	private int num = 1;
	Object obj = new Object();

	@Override
	public void run() {
		while (true) {
			synchronized (obj) { 
				notify();  //同步监视器变为obj,调用方法应为obj.notify()
				if (num <= 100) {
					System.out.println(Thread.currentThread().getName() + ":" + num);
					num++;
					try {
						wait(); //同步监视器变为obj,调用方法应为obj.wait()
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else
					break;
			}
		}
	}
}
  • sleep() 和wait()方法的异同:
    • 一旦进入方法,都可以使当前方法进入阻塞状态
    • 声明位置不同:Thread类中声明sleep(),Object类中声明wait()
    • 调用要求不同:sleep() 可以在任何需要的场景下调用,wait() 必须使用在同步代码块或同步方法中
    • 关于是否释放同步监视器:如果两个方法都使用在同步代码块、同步方法中,sleep() 不会释放同步监视器,wait()会释放同步监视器
练习 – 生产者消费者
public class ProductTest {
	public static void main(String[] args) {
		Clerk clerk = new Clerk();
		Producer producer = new Producer(clerk);
		producer.setName("Producer1");

		Customer customer = new Customer(clerk);
		customer.setName("Customer1");

		producer.start();
		customer.start();
	}
}

class Clerk {
	private int product = 0;

	public synchronized void produce() {
		if (product < 20) {
			product++;
			System.out.println(Thread.currentThread().getName() + ":" + "生产第" + product + "件产品");
			notify();
		} else {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public synchronized void consume() {
		if (product > 0) {
			System.out.println(Thread.currentThread().getName() + ":" + "消费第" + product + "件产品");
			product--;
			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("生产者开始生产...");
		while (true) {
			clerk.produce();
		}
	}
}

class Customer extends Thread {
	private Clerk clerk;

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

	@Override
	public void run() {
		System.out.println("消费者开始消费...");
		while (true) {
			clerk.consume();
		}
	}
}

九、JDK5.0新增创建方式

新增方式一:实现Callable接口
  • 与Runnable相比,Callable功能更强大
    • 相比run() 方法,call() 方法可以有返回值
    • call() 方法可以抛出异常
    • 支持泛型的返回值
    • 需要借助FutureTest类,比如获取返回结果
  • Future接口
    • 可以对具体Runnable、Callable的执行结果进行取消、查询是否完成、获取结果等
    • FutureTask是Futurei接口的唯一实现类
    • FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
  • 具体步骤
    • Step1 创建一个实现Callable接口的实现类
    • Step2 实现call()方法,并将此线程需要执行的操作声明在call()中,call()方法可以有返回值
    • Step3 创建Callable接口实现类的对象
    • Step4 将此Callable接口实现类的对象作为参数传入FutureTest的构造器中,创建FutureTest对象
    • Step5 将FutureTest的对象作为参数传入Thread类的构造器中,创建Thread的匿名对象,并调用start()启动线程
    • Step6 获取Callable中call()方法的返回值
public class CallableTest {
	public static void main(String[] args) {
		// Step3 创建Callable接口实现类的对象
		NumberThread numThread = new NumberThread();
		
		// Step4 将此Callable接口实现类的对象作为参数传入FutureTest的构造器中,创建FutureTest对象
		FutureTask futureTask = new FutureTask(numThread);
		
		// Step5 将FutureTest的对象作为参数传入Thread类的构造器中,创建Thread的匿名对象,并调用start()启动线程
		new Thread(futureTask).start(); // FutureTest实现了Runnable接口
		
		try {
			// Step6 获取Callable中call()方法的返回值
			// get()方法返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值
			System.out.println("总和为:" + futureTask.get());
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}
}

// Step1 创建一个实现Callable接口的实现类
class NumberThread implements Callable {
	
	// Step2 实现call()方法,并将此线程需要执行的操作声明在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;
	}
}
新增方式二:线程池
  • 背景:经常创建和销毁,使用量特别大的资源,比如开发情况下的线程,对性能影响很大
  • 思路:提前创建好多个线程,放入线程池中,使用时可以直接获取,使用完放回池中,可以避免频繁的调用销毁,实现重复利用。
  • 优点
    • 提高相应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • KeepAliveTime:线程没有任务时,最多保持多长时间后终止
  • 相关API
    • ExecutorService 接口:真正的线程池接口
      • ThreadPoolExecutor类
      • void execute (Runnable command): 执行任务/命令,没有返回值,一般执行Runnable
      • < T> Future < T> submit (Callable task): 执行任务,有返回值,一般用来执行Callable
      • void shuldown(): 关闭连接池
    • Executors 工具类:线程池的工厂类,用来创建并返回不同类型的线程池
      • Executors newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
      • Executors newFixedThreadPool(): 创建一个可复用固定线程的线程池
      • Executors newSingleThreadPool(): 创建一个只有一个线程的线程池
      • Executors newScheduledThreadPool(): 创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
public class ThreadPoolTest {
	public static void main(String[] args) {
		// Step1 提供指定线程数的线程池
		ExecutorService service = Executors.newFixedThreadPool(2);
		
		System.out.println(service.getClass()); // java.util.concurrent.ThreadPoolExecutor 
		
		// ThreadPoolExecutor extends AbstractExecutorService -> AbstractExecutorService implements ExecutorService
		ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
		service1.set...

		// Step2 执行指定的线程的操作,需要提供Runnable接口或Callable接口的实现类对象
		service.execute(new NumThread());// 适合使用于Runnable
		service.execute(new NumThread1());// 适合使用于Runnable
		// service.submit(task); // 适合使用于Callable

		// Step3 关闭连接池
		service.shutdown();
	}
}

class NumThread 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 NumThread1 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i <= 100; i++) {
			if (i % 2 != 0) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值