目录
方式三、JDK5.0提供Callable、FutureTask接口
2、ScheduleExecutorService创建定时器
- //使用多线程
-
- public class ThreadDemo1 {
- //默认执行的线程为主线程如main方法
- public static void main(String[] args) {
- //3、new一个新线程对象
- Thread t = new MyThread(); //多态
- //4、调用start方法,实际还是调用run方法
- t.start();
-
- for (int i = 0; i < 5; i++) {
- System.out.println("主线程" + i);
- }
-
- }
- }
-
- //1、定义一个线程类继承thread
- class MyThread extends Thread{
- //2、重写run方法,声明线程要做什么
-
- @Override
- public void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println("子线程" + i);
- }
- }
- }
-
-
- //主线程与子线程同时跑
- 主线程0
- 子线程0
- 主线程1
- 子线程1
- 主线程2
- 子线程2
- 主线程3
- 子线程3
- 主线程4
- 子线程4
- public class Test {
- public static void main(String[] args) {
- Student s = new Student();
- s.run();
- for (int i = 0; i < 5; i++) {
- System.out.println("主测试" + i);
- }
- }
- }
-
- class Student{
-
- public void run(){
- for (int i = 0; i < 5; i++) {
- System.out.println("测试" + i);
- }
- }
- }
-
- //不使用多线程 按顺序进行
- 测试0
- 测试1
- 测试2
- 测试3
- 测试4
- 主测试0
- 主测试1
- 主测试2
- 主测试3
- 主测试4
继承了一个类就不能继承其他类,不利于扩展。
为什么用start调用run方法,而不直接用run方法?
如果直接调用run方法,则会当作普通方法来执行,按顺序执行,而不会以多线程形式进行,start方法是告诉操作系统,调用出开启新的多线程,让CPU将他作为一个单独的执行流程,以线程的方式启动,要将主线程任务放在子线程之后。
- public class ThreadDemo2 {
- public static void main(String[] args) {
- //学会线程创建方式二
-
- //2、创建任务对象,非线程对象
- Runnable r = new MyRunnable();
- //3、将任务对象交给Thread处理,转成线程对象
- Thread t = new Thread(r);
- //4、调用start方法,启动线程
- t.start();
-
- //主线程
- for (int i = 0; i < 3; i++) {
- System.out.println("主线程输出" + i);
- }
- }
- }
-
- class MyRunnable implements Runnable{
- //1、创建任务类继承Runable接口
- @Override
- public void run() {
- for (int i = 0; i < 3; i++) {
- System.out.println("子线程输出" + i);
- }
- }
- }
- public class ThreadDemo3 {
- public static void main(String[] args) {
- //学会线程创建方式二、匿名内部类
-
- //1、创建匿名任务对象
- Runnable r = new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < 3; i++) {
- System.out.println("子线程1输出" + i);
- }
- }
- };
- //2、将任务对象交给Thread处理,转成线程对象
- Thread t = new Thread(r);
- //3、调用start方法,启动线程
- t.start();
-
- //简化写法
- new Thread(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < 3; i++) {
- System.out.println("子线程2输出" + i);
- }
- }
- }).start();
- //再简化
- new Thread(() -> {
- for (int i = 0; i < 3; i++) {
- System.out.println("子线程3输出" + i);
- }
- }).start();
-
-
-
- //主线程
- for (int i = 0; i < 3; i++) {
- System.out.println("主线程1输出" + i);
- }
- }
- }
- public class ThreadDemo4 {
- public static void main(String[] args) {
- //2、创建Callable任务对象
- Callable
call = new MyCallable(100); -
- //3、将Callable任务对象 交给 FutureTask对象
- // FutureTask对象的作用1:FutureTask是Runnable的对象(实现了Runnable接口),可以交给Thread
- // FutureTask对象的作用2:可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
- FutureTask
f = new FutureTask<>(call); //将call转成了Runnable的对象 -
- //4、交给线程处理
- Thread t = new Thread(f);
-
- //5、启动线程
- t.start();
-
- //6、获取线程执行的结果
- try {
- //如果直接用callable对象调用call方法时,call方法可能没执行完就输出了
- //使用get方法一旦发现线程没有执行完他就会让出cpu 暂停执行 直到线程执行完才继续执行
- String s = f.get();
- System.out.println("线程1的结果:" + s);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
-
- //线程2
- Callable
call2 = new MyCallable(200); - FutureTask
f2 = new FutureTask<>(call2); - Thread t2 = new Thread(f2);
- t2.start();
- try {
- String s2 = f2.get();
- System.out.println("线程2的结果:" + s2);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
-
- }
- }
-
- //1、创建任务类继承Callable接口 重写call方法
- class MyCallable implements Callable
{ -
- private int n;
-
- public MyCallable() {
- }
-
- public MyCallable(int n) {
- this.n = n;
- }
-
- @Override
- public String call() throws Exception {
- int sum = 0;
- for (int i = 0; i < n; i++) {
- sum += n;
- }
- return "子线程执行的结果:" + sum;
- }
- }
- public class ThreadAPIDemo {
- public static void main(String[] args) {
- Thread t = new MyThread();
- //设置线程的名字
- t.setName("1号");
- t.start();
-
- Thread t2 = new MyThread();
- t2.setName("2号");
- t2.start();
-
- //获取当前执行的线程对象
- //哪个线程执行它,它就得到哪个线程对象
- //主线程的名称就叫main
- Thread m = Thread.currentThread();
- for (int i = 0; i < 5; i++) {
- System.out.println("main线程输出" + i);
- }
- }
- }
- public class ThreadAPIDemo2 {
- public static void main(String[] args) {
- //直接通过父类构造器创建线程名
- Thread t1 = new Thread("1号");
- t1.start();
- }
- }
-
- //MyThread继承Thread继承父类构造器
- public class MyThread extends Thread{
-
- public MyThread() {
- }
-
- //继承父类构造器
- public MyThread(String name) {
- super(name);
- }
-
- @Override
- public void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName() + "线程输出:" + i);
- }
- }
- }
- public static void main(String[] args) throws InterruptedException {
- for (int i = 1; i < 5; i++) {
- System.out.println("输出:" + i);
-
- if (i == 3){
- //如果i == 3 休眠3秒再执行
- Thread.sleep(3000);
- }
- }
-
- }
两人同时取钱,会使得账户余额变为-10万导致银行亏了10万
- public class RemoveMoney {
- public static void main(String[] args) {
- //新建 账户
- Account a = new Account("共享账户",10);
-
- //两个线程接同一个账户
- //小红线程
- new User("小红",a).start();
-
- //小明线程
- new User("小明",a).start();
-
- }
- }
-
- //线程用户类
- public class User extends Thread{
- //关联账户类对象
- private Account acc;
-
- public User(String name, Account acc) {
- super(name);
- this.acc = acc;
- }
-
- public Account getAcc() {
- return acc;
- }
-
- public void setAcc(Account acc) {
- this.acc = acc;
- }
-
- @Override
- public void run() {
- acc.drawMonry(10);
- }
- }
-
- //账户类
- public class Account {
- private String accountName;
- private double money;
-
- public Account() {
- }
-
- public Account(String accountName, double money) {
- this.accountName = accountName;
- this.money = money;
- }
-
- ...
-
- /**
- * 用户取钱
- * @param i
- */
- public void drawMonry(double i) {
- //判断是哪一个线程执行
- String userName = Thread.currentThread().getName();
-
- //判断账户余额是否充足
- if (i <= this.money){
- System.out.println(userName + "来取钱成功,吐出:" + i);
- //更新余额
- this.money -= i;
- System.out.println(userName + "来取钱剩余:" + this.money);
- }else {
- System.out.println(userName + "取钱,余额不足!");
- }
- }
- }
比如银行取钱,两个用户可以同时访问账户,但是到了取钱业务时开始排队,有先后次序。
- public class Account {
- private String accountName;
- private double money;
-
- .
- .
- .
-
- /**
- * 用户取钱
- * @param i
- */
- public void drawMonry(double i) {
- //判断是哪一个线程执行
- String userName = Thread.currentThread().getName();
- //取钱业务进行加锁,同步代码块
- //默认锁对象
- synchronized ("heima") {
- //判断账户余额是否充足
- if (i <= this.money){
- System.out.println(userName + "来取钱成功,吐出:" + i);
- //更新余额
- this.money -= i;
- System.out.println(userName + "来取钱剩余:" + this.money);
- }else {
- System.out.println(userName + "取钱,余额不足!");
- }
- }
- }
- }
两个线程同步进行到此业务时,其中一个线程抢到锁则进入执行,同时将此业务暂时锁住,后来的线程只能等待先抢到锁的线程执行完毕,才能进入继续执行。
问题引入:锁对象用任意唯一的对象好不好呢?(任意对象但类型唯一)
因为这个锁是定义在账户类中,所有的账户类都共用同一个锁,当不同的线程对象使用不同的账户类时,他们所使用的锁是同一个,因此,线程A操作自己的账户A,线程B操作自己的账户B,当这两个线程同时运行,A操作A账户时,进行加锁,然而锁只有一个,所以B要操作自己的账户B 就必须等待A操作完,因此会影响其他无关线程的执行。
- //对每个账户对象都设了一把独有的锁
- public void drawMonry(double i) {
- //判断是哪一个线程执行
- String userName = Thread.currentThread().getName();
-
- //this 表示当前账户对象,对于不同账户对象会使用不同的锁
- synchronized (this) {
- //判断账户余额是否充足
- if (i <= this.money){
- System.out.println(userName + "来取钱成功,吐出:" + i);
- //更新余额
- this.money -= i;
- System.out.println(userName + "来取钱剩余:" + this.money);
- }else {
- System.out.println(userName + "取钱,余额不足!");
- }
- }
- }
-
-
-
- //对静态方法加锁
- //类名.class
- public static void run(){
- synchronized (Account.class){
-
- }
- }
- //加修饰符对方法加锁
- public synchronized void drawMonry(double i) {
- //判断是哪一个线程执行
- String userName = Thread.currentThread().getName();
-
- //this 表示当前账户对象,对于不同账户对象会使用不同的锁
-
- //判断账户余额是否充足
- if (i <= this.money){
- System.out.println(userName + "来取钱成功,吐出:" + i);
- //更新余额
- this.money -= i;
- System.out.println(userName + "来取钱剩余:" + this.money);
- }else {
- System.out.println(userName + "取钱,余额不足!");
- }
-
- }
但是同步代码块的可读性差,同步方法更容易看出
- public class Account {
- private String accountName;
- private double money;
- //对每个创建的账户类独有一个锁,并且锁唯一,不可被修改
- private final ReentrantLock lock = new ReentrantLock();
- .
- .
- .
-
- public void drawMonry(double i) {
- //判断是哪一个线程执行
- String userName = Thread.currentThread().getName();
-
- //上锁
- lock.lock();
- //判断账户余额是否充足
- try {
- if (i <= this.money) {
- System.out.println(userName + "来取钱成功,吐出:" + i);
- //更新余额
- this.money -= i;
- System.out.println(userName + "来取钱剩余:" + this.money);
- } else {
- System.out.println(userName + "取钱,余额不足!");
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- //放在finally中使解锁一定执行
- //解锁
- lock.unlock();
- }
-
- }
- public static void main(String[] args) {
- //新建账户对象
- Account acc = new Account("CCC-233",0);
-
- //五个人用的同一把锁,锁可以跨方法
- //创建两个取钱线程代表小红、小明
- new UserThread("小红",acc).start();
- new UserThread("小明",acc).start();
-
- //创建三个存钱线程表示三个爸爸
- new DepositThread("岳父",acc).start();
- new DepositThread("干爹",acc).start();
- new DepositThread("亲爹",acc).start();
-
-
- }
-
- public class Account {
- private String accountName;
- private double money;
- //每个账户都有一个自己的锁,不能被更改
- private final ReentrantLock lock = new ReentrantLock();
-
- public Account() {
- }
-
- .
- .
- .
- /**
- * 取钱
- * @param money
- */
- public synchronized void drawMoney(double money) {
- //实例方法中this代表锁对象
- try {
- //取钱
- //获取到当前线程
- String name = Thread.currentThread().getName();
-
- //判断取钱是否大于账户
- if (this.money >= money) {
- this.money -= money;
- System.out.println(name + "取钱" + money + "成功!账户余额:" + this.money);
- //取完钱,没钱了
- //先将其他进程唤醒自己在进入等待
- //用当前进程唤醒所有进程
- this.notifyAll();
- //让当前进程进入等待
- this.wait();
- } else {
- //当前线程进入发现余额不足则唤醒其他线程,自己等待
- //用当前进程唤醒所有进程
- this.notifyAll();
- //让当前进程进入等待
- this.wait();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
-
- }
-
- /**
- * 存钱
- * @param money
- */
- public synchronized void depositMoney(double money) {
- try {
- //存钱
- //获取到当前线程
- String name = Thread.currentThread().getName();
-
- //判断取钱是否大于账户
- if (this.money == 0) {
- this.money += money;
- System.out.println(name + "存钱成功!账户余额:" + this.money);
- //用当前进程唤醒所有进程
- this.notifyAll();
- //让当前进程进入等待
- this.wait();
- } else {
- //还有钱不用存
- //用当前进程唤醒所有进程
- this.notifyAll();
- //让当前进程进入等待
- this.wait();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
假设线程池控制的最多只有三个长久存在的线程,在进入的新任务就不会创建新的线程,会等前三个线程执行完成后再来处理新线程,使线程可以重复利用,避免资源耗尽的风险,并且节约系统内存,因为如果进入一个任务就创建一个线程,线程是占用内存的,当任务过多会挤爆系统。
任务队列表示饭店门口的椅子,并不是实际工作的椅子,也就是允许等待的线程数量
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
CallerRunsPolicy,由主线程负责处理,就是绕过员工交给老板处理
- //Runnable任务对象
- public class MyRunnable implements Runnable{
- @Override
- public void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName() + "输出:Hello world" + "=>" + i);
-
- } try {
- System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠");
- Thread.sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
-
-
- public class ThreadPoolDemo {
- public static void main(String[] args) {
- //1、创建线程池对象
- ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
- new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
- new ThreadPoolExecutor.AbortPolicy());
-
- //2、创建任务对象给线程池处理
- Runnable target = new MyRunnable();
- //三个正式工处理任务
- pool.execute(target);
- pool.execute(target);
- pool.execute(target);
- //又来五个任务在门口等待,设置的任务队列最大为5
- pool.execute(target);
- pool.execute(target);
- pool.execute(target);
- pool.execute(target);
- pool.execute(target);
- //门口椅子坐不下了,开始请临时工
- //开始创建临时线程
- pool.execute(target);
- pool.execute(target);
- //人满了,拒绝营业
- //pool.execute(target);
-
- //关闭线程池(开发中一般不会使用)
- pool.shutdownNow(); //立即关闭,即使任务没有完成,会丢失任务
- pool.shutdown(); //任务跑完才关闭
-
-
-
- }
- }
- //Callable任务对象
- public class MyCallable implements Callable
{ - public int n;
-
- public MyCallable() {
- }
-
- public MyCallable(int n) {
- this.n = n;
- }
-
- @Override
- public String call() throws Exception {
- int sum = 0;
- for (int i = 0; i < n; i++) {
- sum += i;
- }
- return Thread.currentThread().getName() + "执行1-" + n + "的和,输出结果为:" + sum;
- }
- }
-
-
-
-
-
- public class ThreadPoolDemo2 {
- public static void main(String[] args) throws Exception {
- //1、创建线程池对象
- ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
- new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
- new ThreadPoolExecutor.AbortPolicy());
-
- //2、创建任务对象给线程池处理
- /*Callable call = new MyCallable(100);
- pool.submit(call);*/
- //submit可以接Future对象,接返回值
- //三个线程可以执行完成
- Future
f1 = pool.submit(new MyCallable(100)); - Future
f2 = pool.submit(new MyCallable(200)); - Future
f3 = pool.submit(new MyCallable(300)); - //任务队列
- Future
f4 = pool.submit(new MyCallable(400)); - Future
f5 = pool.submit(new MyCallable(500)); - Future
f6 = pool.submit(new MyCallable(600)); - Future
f7 = pool.submit(new MyCallable(600)); - Future
f8 = pool.submit(new MyCallable(600)); - //创建临时线程
- Future
f9 = pool.submit(new MyCallable(600)); - //String s1 = f1.get();
- //取结果会等待任务执行完
- System.out.println(f1.get());
- System.out.println(f2.get());
- System.out.println(f3.get());
- System.out.println(f4.get());
- System.out.println(f5.get());
- System.out.println(f6.get());
- System.out.println(f7.get());
- System.out.println(f8.get());
- System.out.println(f9.get());
- }
- }
- //底层源码
- public static ExecutorService newFixedThreadPool(int nThreads) {
- //创建指定正式工的方法中不会创建临时工
- //因为正式工一旦挂了立马就会有新的线程补上来,相当于不死线程,没有空闲时间
- return new ThreadPoolExecutor(nThreads, nThreads,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue
()); - }
- public class ThreadExecutors {
- public static void main(String[] args) {
- //使用Executors工具类创建线程池
- ExecutorService pool = Executors.newFixedThreadPool(3);
-
- pool.execute(new MyRunnable());
- pool.execute(new MyRunnable());
- pool.execute(new MyRunnable());
- //线程池中只有三个线程,没有多余的线程处理,等前三个任务执行完再来执行
- //进入等待队列
- pool.execute(new MyRunnable());
- }
- }
- public class TimerDemo1 {
- public static void main(String[] args) {
- //Timer定时器创建和使用
- //单线程任务
- Timer timer = new Timer();
- //调用方法,创建定时器
- //3秒之后开始执行,每隔两秒再执行一次。
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + "执行了一次");
- }
- },3000,2000);
- }
- }
- public class ScheduleExecutorSerciceDemo1 {
- public static void main(String[] args) {
- //创建ScheduleExecutorService线程池做定时器
- ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
-
- //开启定时任务
- //TimerTask实现了Runnable接口
- pool.scheduleAtFixedRate(new TimerTask() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + "执行了--AAA");
- }
- },2,3, TimeUnit.SECONDS);
- }
- }
并发:一个CPU对多个进程服务,一次只服务一个进程,但是速度特别快,使得每个进程都被服务。
并行 :CPU多个线程同时执行,并且每个线程可以服务多个进程,所有并行与并发同时进行。
生命周期
sleep状态的线程是不需要抢锁 ,一旦时间到会直接变成可运行的进程,因为它休眠之后不会将锁释放,休眠结束直接运行。
wait状态,一旦进入就会将锁释放给别人,等待结束后得到锁再继续运行。