1、基础概念
1.1程序、进程、线程
·程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
·进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态 的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
·线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开 销小
一个进程中的多个线程共享相同的内存单元/内存地址空间
它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。
一个进程对应一个方法区和堆,一个线程对应一个虚拟机栈和程序计数器
1.2单核和多核
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
1.3使用多线程的优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改
何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等。
需要一些后台运行的程序时。
1.4线程的分类
(1)守护线程:垃圾回收线程、异常处理线程
(2)用户线程:主线程
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
2.线程的创建和使用
2.1多线程的创建(继承)
注意:run()由于是继承而来的,Thread和Runnable没有抛出异常,所以,重写的时候,run()只能try/catch,不能抛出异常
Thread类的特性
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常 把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
方式一:继承于Thread类
-
创建一个继承于Thread类的子类
-
重写Thread类的run() --> 将此线程执行的操作声明在run()中
-
创建Thread类的子类的对象
-
通过此对象调用start()
package linyinlei.java; /** * 多线程的创建,方式一:继承于Thread类 * 1. 创建一个继承于Thread类的子类 * 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中 * 3. 创建Thread类的子类的对象 * 4. 通过此对象调用start() * <p> * 例子:遍历100以内的所有的偶数 * * @author linyinlei * @create 2019-02-13 上午 11:46 */ //1. 创建一个继承于Thread类的子类 class MyThread extends Thread { //2. 重写Thread类的run() @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3. 创建Thread类的子类的对象 MyThread t1 = new MyThread(); //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run() t1.start(); //问题一:我们不能通过直接调用run()的方式启动线程。这其实是对象调用方法 // t1.run(); //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException t1.start(); //我们需要重新创建一个线程的对象 MyThread t2 = new MyThread(); t2.start(); //如下操作仍然是在main线程中执行的。 for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************"); } } } }
2.2多线程的创建(Runnable接口)
方式二:实现Runnable接口
创建一个实现了Runnable接口的类
实现类去实现Runnable中的抽象方法:run()
创建实现类的对象
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()
package linyinlei.java; /** * 比较创建线程的两种方式。 * 开发中:优先选择:实现Runnable接口的方式 * 原因:1. 实现的方式没有类的单继承性的局限性 * 2. 实现的方式更适合来处理多个线程有共享数据的情况。 * * 联系:public class Thread implements Runnable * 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。 * * @author linyinlei * @create 2019-02-13 下午 4:34 */ //1. 创建一个实现了Runnable接口的类 class MThread implements Runnable{ //2. 实现类去实现Runnable中的抽象方法:run() @Override public void 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) { //3. 创建实现类的对象 MThread mThread = new MThread(); //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 Thread t1 = new Thread(mThread); t1.setName("线程1"); //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run() t1.start(); //再启动一个线程,遍历100以内的偶数 Thread t2 = new Thread(mThread); t2.setName("线程2"); t2.start(); } }
2.2创建两个分线程
package linyinlei.exer; /** * @author linyinlei * @create 2022-02-02 16:37 */ //创建两个分线程 class MyThread1 extends Thread{ @Override public void run() { for(int i = 0;i < 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } class MyThread2 extends Thread{ public void run() { for(int i = 0;i < 100;i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } public class ThreadDemo { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); t1.start(); t2.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(); } }
2.3进程的方法及优先级
package linyinlei.exer; /** * 测试Thread中的常用方法: * 1. start():启动当前线程;调用当前线程的run() * 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 * 3. currentThread():静态方法,返回执行当前代码的线程 * 4. getName():获取当前线程的名字 * 5. setName():设置当前线程的名字 * 6. yield():释放当前cpu的执行权 * 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才 * 结束阻塞状态。 * 8. stop():已过时。当执行此方法时,强制结束当前线程。 * 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前 * 线程是阻塞状态。 * 10. isAlive():判断当前线程是否存活 * <p> * <p> * 线程的优先级: * 1. * MAX_PRIORITY:10 * MIN _PRIORITY:1 * NORM_PRIORITY:5 -->默认优先级 * 2.如何获取和设置当前线程的优先级: * getPriority():获取线程的优先级 * setPriority(int p):设置线程的优先级 * <p> * 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下 * 被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。 * * @author linyinlei * @create 2019-02-13 下午 2:26 */ class HelloThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { try { sleep(10); //阻塞等待一秒,需要cpu给资源,但是cpu现在在执行别的代码,分配不了资源,只能等别的资源执行完再分配 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); } /* if(i % 20 == 0){ this.yield(); }*/ } } public HelloThread(String name) { super(name); } } public class ThreadMethodTest { public static void main(String[] args) { Thread.currentThread().setName("主线程"); //因为Thread类中有空参构造器,又有无参构造器 HelloThread h1 = new HelloThread("Thread:1"); //给子线程命名,利用无参构造器 //h1.setName("线程一"); h1.start(); for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } if (i == 20){ try { h1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(h1.isAlive()); } }
卖票项目的第一种方式(不安全)
package atguigu.java; /** * * 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式 * * 存在线程的安全问题,待解决。 * * @author linyinlei * @create 2019-02-13 下午 4:20 */ class Window extends Thread{ private static int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(getName() + ":卖票,票号为:" + ticket); ticket--; }else{ break; } } } } public class WindowTest { public static void main(String[] args) { Window t1 = new Window(); Window t2 = new Window(); Window t3 = new Window(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } //结果如下,三个人抢了同一张票,恐怖吧 }
卖票项目的第二种方式(ticket变量不用static,就能被共用,因为三个都是用的一个对象)
package linyinlei.java; /** * 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式 * 存在线程的安全问题,待解决。 * * @author linyinlei * @create 2019-02-13 下午 4:47 */ class Window1 implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; }else{ break; } } } } public class WindowTest1 { 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("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
2.4比较线程的两种方式
开发中,优先选择:实现Runnable接口的方式
原因:-
实现的方式没有类的单继承性的局限性
-
实现的方式更适合来处理多个线程有共享数据(ticket不用static也能被共用)的情况。
联系:public class Thread implements Runnable,最终用的Thread,也是继承了Runnable接口,重写了run
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
-
3.线程的生命周期
JDK中用Thread.State类定义了线程的几种状态 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态 //join();
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
4.线程的同步
4.1为什么不安全?
1.多个线程执行的不确定性引起执行结果的不稳定
2.多个线程对账本的共享,会造成操作的不完整性,会破坏数据
创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
存在线程的安全问题
1.问题:卖票过程中,出现了重票、错票 出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成中,其他线程参与进来,也操作车票(最大的原因是因为数据被共享)
3.如何解决:当一个线程a在线程ticket的时候,其他线程不能参与进来,直到线程a结束,
线程b才可以开始操作ticket,这种情况即使线程a出现了阻塞,也不能被改变
4.在java中,我们通过同步机制,来解决线程的安全问题
4.2两种方法
方法一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.多个线程共同操作的变量,比如:ticket就是共享数据
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
要求:多个线程共用一把锁
4.同步的方式,解决了线程的安全问题,操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步
总计:
1.同步方法仍然涉及到同步监视器,知识不需要我们显式的声明
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身的对象
第一种卖票的改进(同步代码块)
package linyinlei.java;
/**
* 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
*
* 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当监视器
*
* @author linyinlei
* @create 2019-02-13 下午 4:20
*/
class Window extends Thread {
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized(Window.class){
//window.class本质为一个对象
//错误的synchronized(this) this代表的是t1,t2,t3三个对象
//正确的 synchronized (obj) {
if (ticket > 0) {
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
第二种卖票的改进(同步代码块)
package linyinlei.java;
/**
* 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
* 存在线程的安全问题
* 1.问题:卖票过程中,出现了重票、错票 出现了线程的安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成中,其他线程参与进来,也操作车票(最大的原因是因为数据被共享)
* 3.如何解决:当一个线程a在线程ticket的时候,其他线程不能参与进来,直到线程a结束,
* 线程b才可以开始操作ticket,这种情况即使线程a出现了阻塞,也不能被改变
* 4.在java中,我们通过同步机制,来解决线程的安全问题
*
*
* 方法一:同步代码块
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:
* 1.操作共享数据的代码,即为需要被同步的代码,不能多也不能少,若把while(true)包含进去,
* 即多个线程的一个进去了,其他的被阻塞在外面,进去的哪一个把所有的票都卖完出来告诉两个
* 等着的人说没有了,那多对象和锁的意义就没有了,体现不出多窗口
* 2.多个线程共同操作的变量,比如:ticket就是共享数据
* 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
* 要求:多个线程共用一把锁
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
* 方式二:同步方法
*
* 5.同步的方式,解决了线程的安全问题
* 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
* @author linyinlei
* @create 2019-02-13 下午 4:47
*/
class Window1 implements Runnable {
private int ticket = 100;
//private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized(this){
//this指的是唯一的window1的对象
//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 WindowTest1 {
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("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
第一种卖票方式的同步方法
package linyinlei.java;
/**同步方法处理继承Thread类的线程安全问题
*
* @author linyinlei
* @create 2022-02-03 10:39
*/
class Window3 extends Thread {
private static int ticket = 100;
// private static Object obj = new Object();
Boolean flag;
@Override
public void run() {
flag = true;
while (flag) {
show();
}
}
private static synchronized void show() {//同步监视器为Window3.class
//window.class本质为一个对象
//错误的synchronized(this) this代表的是t1,t2,t3三个对象
//正确的 synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
else{
flag = false;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
第二种方法的同步方法
package linyinlei.java;
/**使用同步方法解决实现Runnable接口的线程安全问题
*
* @author linyinlei
* @create 2022-02-03 10:27
*/
class Window2 implements Runnable {
private int ticket = 100;
//private Object obj = new Object();
Boolean flag = true;
@Override
public void run() {
while (flag) {
show();
}
}
private synchronized void show(){//这里的同步监视器为this
if (ticket > 0) {
try {
Thread.sleep(100);//延长时间,增大多个线程引用同一共享数据而引发的不安全的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
else{
flag = false;
}
}
}
public class WindowTest2{
public static void main(String[] args) {
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
4.3解决懒汉式的线程不安全
package linyinlei.java;
/**
* 使用线程的同步解决单例模式的懒汉式
*
* @author linyinlei
* @create 2022-02-03 11:03
*/
public class BankTest {
}
class Bank {
private Bank(){
}
private static Bank instance = null;//共享数据
public static Bank getInstance() {
//方式一:效率稍差,没有告知等候的线程走
/* synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
return instance;
}*/
//方式二:
if(instance == null){
synchronized (Bank.class){
if (instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
4.4线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
sleep加大了死锁的概率,而不是说不会出现
package linyinlei.java;
//死锁的演示
class A {
public synchronized void foo(B b) { //同步监视器:B类的对象:b
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了A实例的foo方法"); // ①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用B实例的last方法"); // ③
b.last();//这里的b为传过来的对象
}
public synchronized void last() {//同步监视器:A类的对象:a
System.out.println("进入了A类的last方法内部");
}
}
class B {
public synchronized void bar(A a) {//同步监视器:b
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了B实例的bar方法"); // ②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用A实例的last方法"); // ④
a.last();
}
public synchronized void last() {//同步监视器:b
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock1 implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");
// 调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
// 调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock1dl = new DeadLock1();
new Thread(dl).start();
dl.init();
}
}
4.4解决线程安全的第三种方式lock锁(JDK 5.0新增)
属于Reentrantlock类
1.区别
-
面试题:synchronized 与 Lock的异同?
-
相同:二者都可以解决线程安全问题
-
不同:1.synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
-
2.优先使用顺序:
- 1.Lock
- 2.同步代码块(已经进入了方法体,分配了相应资源)
- 3.同步方法(在方法体之外)
package linyinlei.java;
import java.util.concurrent.locks.ReentrantLock;
/*
* 面试题:如何解决线程安全问题?有几种方式
* @author linyinlei
* @create 2019-02-15 下午 3:38
*/
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
5.线程的通信
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
说明:
1.wait(),notify(),notifyAll()三个方法必须使用再同步代码块或同步方法中
2.三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则,会出现IllegalMonitorStateException
3.wait(),notify(),notifyAll()三个方法是定义再java.lang.Object类中,方便任何一个对象的调用
面试题:
sleep() wait()的异同
同:一旦执行方法,都可以使得当前线程进入阻塞状态
异:1)两个方法声明的位置不同:Thread类中声明sleep(),Object()类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块和同步方法中
3)如果两个方法都使用子啊同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
5.1线程通信的应用1:
使用两个线程打印 1-100。线程1, 线程2 交替打印
package linyinlei.java;
/**
* @author linyinlei
* @create 2022-02-04 10:53
*/
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
//notifyAll();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程为" + Thread.currentThread().getName() + ",现打印" + number);
number++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number num = new Number();
Thread t1 = new Thread(num);
Thread t2 = new Thread(num);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
5.2线程通信的应用2:
经典例题:生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
分析:
- 是否是多线程问题?是,生产者线程,消费者线程
- 是否有共享数据?是,店员(或产品)
- 如何解决线程的安全问题?同步机制,有三种方法
- 是否涉及线程的通信?是
package linyinlei.java;
/**
* @author linyinlei
* @create 2022-02-15 下午 4:48
*/
class Clerk {
private int productNum = 0;
public Clerk() {
}
public void produceProduct() {
synchronized (this) {
if (productNum < 20) {
productNum++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productNum + "个产品");
notifyAll();
}
else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void consunmeProduct() {
synchronized (this) {
if (productNum > 0) {
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productNum + "个产品");
productNum--;
notifyAll();
}
else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Productor implements Runnable {
private Clerk clerk;
public Productor() {
}
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始生产产品");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Customer implements Runnable {
private Clerk clerk;
public Customer() {
}
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始消费产品");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consunmeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro1 = new Productor(clerk);
Productor pro2 = new Productor(clerk);
Customer cust1 = new Customer(clerk);
Customer cust2 = new Customer(clerk);
//操作的是同一个clerk中的数据,produceNum被共享,当一个线程在进行的时候,由于生产和消费共用
//同一个监视器,所以只有等一个任务结束,才会执行下一个任务,保证了线程的同步和通信
Thread t1 = new Thread(pro1);
t1.setName("生产者一");
Thread t2 = new Thread(cust1);
t2.setName("顾客一");
Thread t3 = new Thread(pro2);
t3.setName("生产者二");
Thread t4 = new Thread(cust2);
t4.setName("顾客二");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
6.JDK 5.0新增2种线程创建方式
6.1实现Callable接口
与使用Runnable相比
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
Future接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为
Runnable被线程执行,又可以作为Future得到Callable的返回值
package linyinlei.java;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author linyinlei
* @create 2022-02-04 14:49
*/
//1.创建一个实现Callable的实现类
class Number1 implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0 ;
for(int i = 1;i <= 100;i++){
if(i % 2 ==0){
System.out.println("0-100内的偶数有:"+i);
sum += i;
}
}
String str =new String();
str = "0-100之内的偶数和为"+sum;
return str;
//return sum; sum本是int类型,可是确实Object声明,自动转换为包装类 Number
}
}
public class ThreadNew1 {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
Number1 numThread = new Number1();
//4.将此Callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
FutureTask futureTask= new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
//下面的东西,有需要return的值就调用,不需要可以不用get()
try {
//6.获取Callable中的call方法的返回值
//get()返回值即为futureTast构造器参数Callable实现类重写的call()的返回值
Object str = futureTask.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解实现Callable接口的方式创建多线程比实现Runnable接口强大?
1.call()可以有返回值的
2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
3,Callable是支持泛型的
package linyinlei.java;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author linyinlei
* @create 2022-02-04 14:49
*/
//1.创建一个实现Callable的实现类
class Number1 implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0 ;
for(int i = 1;i <= 100;i++){
if(i % 2 ==0){
System.out.println("0-100内的偶数有:"+i);
sum += i;
}
}
String str =new String();
str = "0-100之内的偶数和为"+sum;
return str;
//return sum; sum本是int类型,可是确实Object声明,自动转换为包装类 Number
}
}
public class ThreadNew1 {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
Number1 numThread = new Number1();
//4.将此Callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
FutureTask futureTask= new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
//下面的东西,有需要return的值就调用,不需要可以不用get()
try {
//6.获取Callable中的call方法的返回值
//get()返回值即为futureTast构造器参数Callable实现类重写的call()的返回值
Object str = futureTask.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
6.2线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建) 便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
…
4.JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
1).ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
void shutdown() :关闭连接池
2)Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运
6.2.1Runnable类型的
package linyinlei.java;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author linyinlei
* @create 2022-02-05 0:56
*/
class NumberThread implements Runnable {
@Override
public void run() {
for (int num = 1; num <= 100; num++) {
if (num % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
}
}
}
}
class NumberThread1 implements Runnable {
@Override
public void run() {
for (int num = 1; num <= 100; num++) {
if (num % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
}
}
}
}
public class ThreadNew3 {
public static void main(String[] args) {
NumberThread num = new NumberThread();
NumberThread1 num1 = new NumberThread1();
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(50);
//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(num);
service.execute(num);
service.execute(num1);
service.execute(num1);
//3.关闭连接池
service.shutdown();
}
}
6.2.2Callable类型的
package linyinlei.java;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author linyinlei
* @create 2022-02-05 0:56
*/
class NumberThread extends Thread {
@Override
public void run() {
for (int num = 1; num <= 100; num++) {
if (num % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
}
}
}
}
class NumberThread1 extends Thread{
@Override
public void run() {
for (int num = 1; num <= 100; num++) {
if (num % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
}
}
}
}
public class ThreadNew3 {
public static void main(String[] args) {
NumberThread num = new NumberThread();
NumberThread1 num1 = new NumberThread1();
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(50);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
//service1.setCorePoolSize(15);
//service1.setKeepAliveTime(time);
//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.submit(num);
service.submit(num);
service.submit(num1);
service.submit(num1);
//3.关闭连接池
service.shutdown();
}
}
7.练习
7.1题目1:
银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,再拿出两百,存取3次。每次存完,打印账户余额。
分析:
1.是多线程,有两个储户线程
2.有共享数据,账户的余额
3.有线程安全问题
4.三种同步机制解决线程安全问题
Runnable:锁
package linyinlei.exer;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author linyinlei
* @create 2022-02-03 14:54
*/
class Accout {
private double balance;
private ReentrantLock lock = new ReentrantLock();
public Accout() {
}
public Accout(double balance) {
this.balance = balance;
}
public void deposit(double amt) {
lock.lock();
if (amt > 0) {
balance += amt;
System.out.println("当前线程为"+Thread.currentThread().getName()+"存钱成功,余额为" + balance);
lock.unlock();
}
}
public void withdraw(double with) {
lock.lock();
if (with > 0 || balance >= with) {
balance -= with;
System.out.println("当前线程为"+Thread.currentThread().getName()+"取钱成功,余额为" + balance);
lock.unlock();
}
}
public double getBalance() {
return balance;
}
}
class Customer implements Runnable {
private Accout accout;
public Customer() {
}
public Customer(Accout accout) {
this.accout = accout;
}
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
accout.deposit(1000);
accout.withdraw(100);
}
}
}
public class AccoutTest {
public static void main(String[] args) {
Accout accout = new Accout(0);
Customer customer = new Customer(accout);
Thread t1 = new Thread(customer);
Thread t2 = new Thread(customer);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
Runnable:synchronized同步方法
package linyinlei.exer;
/**
* @author linyinlei
* @create 2022-02-03 17:30
*/
class Accout3 {
private double balance;
public Accout3() {
}
public Accout3(double balance) {
this.balance = balance;
}
public synchronized void deposit(double amt) {
if (amt > 0) {
balance += amt;
System.out.println("当前线程为" + Thread.currentThread().getName() + "存钱成功,余额为" + balance);
}
}
public synchronized void withdraw(double with) {
if (with > 0 || balance >= with) {
balance -= with;
System.out.println("当前线程为" + Thread.currentThread().getName() + "取钱成功,余额为" + balance);
}
}
public double getBalance() {
return balance;
}
}
class Customer3 implements Runnable {
private Accout3 accout;
public Customer3() {
}
public Customer3(Accout3 accout) {
this.accout = accout;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Customer3.class) {
accout.deposit(1000);
}
accout.withdraw(100);
}
}
}
public class AccoutTest3 {
public static void main(String[] args) {
Accout3 accout = new Accout3(0);
Customer3 customer = new Customer3(accout);
Thread t1 = new Thread(customer);
Thread t2 = new Thread(customer);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
Runnable:synchronized同步代码块
package linyinlei.exer;
/**
* @author linyinlei
* @create 2022-02-03 17:30
*/
class Accout3 {
private double balance;
public Accout3() {
}
public Accout3(double balance) {
this.balance = balance;
}
public void deposit(double amt) {
synchronized (this) {
if (amt > 0) {
balance += amt;
System.out.println("当前线程为" + Thread.currentThread().getName() + "存钱成功,余额为" + balance);
}
}
}
public void withdraw(double with) {
synchronized (this) {
if (with > 0 || balance >= with) {
balance -= with;
System.out.println("当前线程为" + Thread.currentThread().getName() + "取钱成功,余额为" + balance);
}
}
}
public double getBalance() {
return balance;
}
}
class Customer3 implements Runnable {
private Accout3 accout;
public Customer3() {
}
public Customer3(Accout3 accout) {
this.accout = accout;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Customer3.class) {
accout.deposit(1000);
}
accout.withdraw(100);
}
}
}
public class AccoutTest3 {
public static void main(String[] args) {
Accout3 accout = new Accout3(0);
Customer3 customer = new Customer3(accout);
Thread t1 = new Thread(customer);
Thread t2 = new Thread(customer);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
继承:synchronized同步方法
package linyinlei.exer;
/**
* @author linyinlei
* @create 2022-02-03 17:03
*/
/**
* @author linyinlei
* @create 2022-02-03 14:54
*/
class Accout2 {
private static double balance;
public Accout2() {
}
public Accout2(double balance) {
this.balance = balance;
}
public static synchronized void deposit(double amt) {
if (amt > 0) {
balance += amt;
System.out.println("当前线程为"+Thread.currentThread().getName()+"存钱成功,余额为" + balance);
}
}
public static synchronized void withdraw(double with) {
if (with > 0 || balance >= with) {
balance -= with;
System.out.println("当前线程为"+Thread.currentThread().getName()+"取钱成功,余额为" + balance);
}
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
public class AccoutTest2 {
public static void main(String[] args) {
Accout2 accout = new Accout2(0);
Customer2 customer1 = new Customer2(accout);
Customer2 customer2 = new Customer2(accout);
customer1.setName("线程一");
customer2.setName("线程二");
customer1.start();
customer2.start();
}
}
class Customer2 extends Thread {
private Accout2 accout;
public Customer2() {
}
public Customer2(Accout2 accout) {
this.accout = accout;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(Customer2.class){
accout.deposit(1000);
}
accout.withdraw(200);
}
}
}
继承:synchronized同步代码块
package linyinlei.exer;
/**
* @author linyinlei
* @create 2022-02-03 17:03
*/
/**
* @author linyinlei
* @create 2022-02-03 14:54
*/
class Accout2 {
private static double balance;
public Accout2() {
}
public Accout2(double balance) {
this.balance = balance;
}
public static void deposit(double amt) {
synchronized (Accout2.class) {
if (amt > 0) {
balance += amt;
System.out.println("当前线程为" + Thread.currentThread().getName() + "存钱成功,余额为" + balance);
}
}
}
public static void withdraw(double with) {
synchronized (Accout2.class) {
if (with > 0 || balance >= with) {
balance -= with;
System.out.println("当前线程为" + Thread.currentThread().getName() + "取钱成功,余额为" + balance);
}
}
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
class Customer2 extends Thread {
private Accout2 accout;
public Customer2() {
}
public Customer2(Accout2 accout) {
this.accout = accout;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Customer2.class ) {
accout.deposit(1000);
}
accout.withdraw(200);
}
}
}
public class AccoutTest2 {
public static void main(String[] args) {
Accout2 accout = new Accout2(0);
Customer2 customer1 = new Customer2(accout);
Customer2 customer2 = new Customer2(accout);
customer1.setName("线程一");
customer2.setName("线程二");
customer1.start();
customer2.start();
}
}
继承:锁
package linyinlei.exer;
/**
* @author linyinlei
* @create 2022-02-03 16:22
*/
import java.util.concurrent.locks.ReentrantLock;
/**
* @author linyinlei
* @create 2022-02-03 14:54
*/
class Accout1 {
private static double balance;
private static ReentrantLock lock = new ReentrantLock();
public Accout1() {
}
public Accout1(double balance) {
this.balance = balance;
}
public static void deposit(double amt) {
try{
lock.lock();
if (amt > 0) {
balance += amt;
System.out.println("当前线程为"+Thread.currentThread().getName()+"存钱成功,余额为" + balance);
}
}
finally{
lock.unlock();
}
}
public static void withdraw(double with) {
try{
lock.lock();
if (with > 0 || balance >= with) {
balance -= with;
System.out.println("当前线程为"+Thread.currentThread().getName()+"取钱成功,余额为" + balance);
}
}
finally{
lock.unlock();
}
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
public class AccoutTest1 {
public static void main(String[] args) {
Accout1 accout = new Accout1(0);
Customer1 customer1 = new Customer1(accout);
Customer1 customer2 = new Customer1(accout);
customer1.setName("线程一");
customer2.setName("线程二");
customer1.start();
customer2.start();
}
}
class Customer1 extends Thread {
private Accout1 accout;
public Customer1() {
}
public Customer1(Accout1 accout) {
this.accout = accout;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
accout.deposit(1000);
accout.withdraw(200);
}
}
}
7.2题目2:
使用两个线程打印 1-100。线程1, 线程2 交替打印
RenntrateLock lock = new ReentrateLock(true);
带上参数true,可以实现线程公平;
不带参数,就是交互竞争;
package linyinlei.java;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author linyinlei
* @create 2022-02-04 10:53
*/
class Number implements Runnable {
private int number = 1;
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
lock.lock();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程为" + Thread.currentThread().getName() + ",现打印" + number);
number++;
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number num = new Number();
Thread t1 = new Thread(num);
Thread t2 = new Thread(num);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
7.3.1用Callable实现多线程(第一种方式)
package linyinlei.java;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author linyinlei
* @create 2022-02-04 16:07
*/
class Number2<ticket> implements Callable {
private int ticket = 100;
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
@Override
public Object call() throws InterruptedException {
while (true) {
synchronized (this) {
if (this.ticket > 0) {
Thread.sleep(10);
System.out.println("第" + ticket + "张票卖给" + Thread.currentThread().getName());
this.ticket--;
} else {
break;
}
}
}
return 0;
}
}
public class ThreadNew {
public static void main(String[] args) {
Number2 num = new Number2();
//实例化一个继承Callable接口,但Callable功能还未完善的
//让FutureTask这个类去实现它
FutureTask futureTask1 = new FutureTask(num);
FutureTask futureTask2 = new FutureTask(num);
Thread t1 = new Thread(futureTask1);
Thread t2 = new Thread(futureTask2);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
try {
Object ticket = futureTask1.get();
System.out.println(ticket);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
Object ticket = futureTask2.get();
System.out.println(ticket);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
7.3.2用Callable实现多线程(第二种方式)
package linyinlei.java;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author linyinlei
* @create 2022-02-04 16:07
*/
class Number2<ticket> implements Callable {
private int ticket = 100;
Boolean flag;
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
@Override
public Object call() {
flag = true;
while (flag) {
show();
}
return 0;
}
private synchronized void show() {
if (this.ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + ticket + "张票卖给" + Thread.currentThread().getName());
this.ticket--;
}
else{
flag = false;
}
}
}
public class ThreadNew {
public static void main(String[] args) {
Number2 num = new Number2();
//实例化一个继承Callable接口,但Callable功能还未完善的
//让FutureTask这个类去实现它
FutureTask futureTask1 = new FutureTask(num);
FutureTask futureTask2 = new FutureTask(num);
Thread t1 = new Thread(futureTask1);
Thread t2 = new Thread(futureTask2);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
try {
Object ticket = futureTask1.get();
System.out.println(ticket);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
Object ticket = futureTask2.get();
System.out.println(ticket);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
7.3.3用Callable实现多线程(第三种方式)
package linyinlei.java;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author linyinlei
* @create 2022-02-04 16:07
*/
class Number2<ticket> implements Callable {
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
@Override
public Object call() {
while (true) {
try {
lock.lock();
if (this.ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + ticket + "张票卖给" + Thread.currentThread().getName());
this.ticket--;
} else {
break;
}
} finally {
lock.unlock();
}
}
return 0;
}
}
public class ThreadNew {
public static void main(String[] args) {
Number2 num = new Number2();
//实例化一个继承Callable接口,但Callable功能还未完善的
//让FutureTask这个类去实现它
FutureTask futureTask1 = new FutureTask(num);
FutureTask futureTask2 = new FutureTask(num);
Thread t1 = new Thread(futureTask1);
Thread t2 = new Thread(futureTask2);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
try {
Object ticket = futureTask1.get();
System.out.println(ticket);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
Object ticket = futureTask2.get();
System.out.println(ticket);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}