多线程内容

多线程

线程简述

什么是进程?什么是线程?

进程是一个应用程序。(一个进程是一个软件)

线程是一个进程中的执行场景/执行单元

一个进程可以启动多个线程

进程和线程是什么关系

进程A和进程B的内存独立不共享

线程A和线程B(在java语言中):

​ 线程A和线程B,堆内存和方法区内存共享

​ 但是栈内存独立,一个线程一个栈

​ 假设启动10个线程,会有10个栈空间,每个栈之间互不干扰,各自执行各自的,这就是多线程并发

​ java中之所以有多线程机制,目的就是为了提高程序的处理效率

问题思考:

使用多线程机制之后,main方法结束,是不是有可能程序也不会结束?

答:main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈

TT0inK.png

对于单核的CPU来讲,真的可以做到真正的多线程并发吗?

​不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核CPU来说,它们处理是极其快速的,多个线程之间频繁的切换,给人一种并发的感觉。但其实不是并发的
对于多核的CPU电脑来说,真正的多线程并发是没问题的。

四核CPU表示同一个时间点上,可以真正的有4个进程并发执行

什么是真正的多线程并发?

t1线程执行t1的。

t2线程执行t2的。

t1不会影响t2,t2也不会影响t1.这才叫真正的多线程并发。

线程的三种实现方式

java语言中,实现线程有三种方式,先来看一下前两种,最后一种放在后面了

java支持多线程机制。并且java将多线程实现了,我们只需要继承就行了。

注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。

第一种方式:

编写一个类,直接继承java.lang.Thread,重写run方法

package com.bjpowernode.thread;
/*
实现线程的第一种方式:
怎么创建线程对象?     MyThread myThread = new MyThread();
怎么启动线程?调用线程对象的start方法

注意:方法体当中的代码永远都是自上而下的顺序一次逐行执行的
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主栈中运行
        //新建一个分支线程对象
        MyThread t = new MyThread();
        //启动线程
        //t.run();
        // 直接调用run方法和调用start方法的区别是什么?
        //直接调用run方法不会启动线程,不会分配新的分支栈(这种方式就是单线程)
        //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈开出来,start()方法就结束了。线程就启动成功了。
        //启动成功的线程会自动调用run()方法,并且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
        t.start();
        //这里的代码还是运行在主线程中
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--->"+i);
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程--->" + i);

        }
    }
}

线程的start:

TTBErV.png
线程的run:
TTBVbT.png

第二种方式
package com.bjpowernode.thread;

public class ThreadTest03 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        //MyRunnable r = new MyRunnable();
        //Thread t = new Thread(r);
        //以上两行可合并
        Thread t = new Thread(new MyRunnable());
        //启动线程
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程--->"+i);
        }

    }
}
//这并不是一个线程类,是一个可运行的类。它还不是一个线程
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("分支线程--->"+i);
        }

    }
}

还可以用匿名内部类的形式

package com.bjpowernode.thread;
/*
采用匿名内部类可以吗?
 */
public class ThreadTest04 {
    public static void main(String[] args) {
        //创建线程对象,采用匿名内部类方式。
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("t线程--->"+i);

                }
            }
        });
        //启动线程
        t.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main线程--->"+i);

        }
    }
}

线程的生命周期

新建状态
就绪状态
运行状态
阻塞状态
死亡状态
TbPGnS.png

线程对象

1.获取,修改线程对象的名字:

获取线程名字:
String name = 线程对象.getName();
修改线程对象的名字:
线程对象.setName(“线程名字”)
当线程没有设置名字的时候,默认的名字有什么规律?
Thread-0
Thread-1
Thread-2
Thread-3

2.获取当前线程对象

Thread currentThread = Thread.currentThread();

package com.bjpowernode.thread;
/*
1.怎么获取当前线程对象
2.获取线程对象的名字
3.修改线程对象的名字
 */
public class ThreadTest05 {
    public static void main(String[] args) {
        //currentThread就是当前线程对象
        //这个代码出现在main方法当中,所以当前线程就是主线程
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());//main
        //创建线程对象
        MyThread2 t = new MyThread2();
        //设置线程的名字
        //t.setName("tttt");
        //获取线程的名字
        String tName = t.getName();
        System.out.println(tName);//Thread-0
        t.start();
        MyThread2 t2 = new MyThread2();
        System.out.println(t2.getName());//Thread-1
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //currentThread就是当前线程对象,当前线程是谁呢?
            //当t1线程执行run方法,那么这个当前线程就是t1
            //当t2线程执行run方法,那么这个当前线程就是t2
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName()+"-->" +i);
        }
    }
}

线程的sleep方法

sleep方法实例
package com.bjpowernode.thread;
/*
线程的sleep方法:
static void sleep(long millis)
1.静态方法Thread.sleep(1000)
2.参数是毫秒
3.作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用。
4.Thread.sleep();方法,可以实现:
间隔特定的时间去执行一段特定的代码
 */
public class ThreadTest06 {
    public static void main(String[] args) {
//        //当前线程进入休眠,睡眠5s
//        //当前线程就是主线程!!!
//        try {
//            Thread.sleep(1000 * 5);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        System.out.println("5s后输出Hello world");

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
            //睡眠1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

看一道sleep的面试题
package com.bjpowernode.thread;

public class ThreadTest07 {
    public static void main(String[] args) {
        //创建线程对象
        MyThread3 t = new MyThread3();
        t.setName("t");
        t.start();

        //调用sleep方法
        try {
            //这行代码会让线程t进入休眠状态吗?
            t.sleep(1000*5);//在执行的时候还是会转换成:Thread.sleep(1000*5)
                                    //这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。
                                    //这行代码出现在main方法中,main线程休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Hello World!");

    }
}
class MyThread3 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);

        }
    }
}

唤醒正在休眠的线程

案例

package com.bjpowernode.thread;
/*
怎么唤醒一个正在睡眠的线程?
注意:这个不是终断线程的执行,是终止线程的睡眠

 */
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();
        //希望5s后线程醒来
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制)
        t.interrupt();

    }
}
class MyRunnable2 implements Runnable{
    //重点:run()当中的异常不能throw,只能try catch
    //因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--->begin");
        //睡眠一年
        try {
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"--->end");

    }
}

终止一个线程的执行的方法

stop方法(已过时,不推荐使用)
package com.bjpowernode.thread;
/*
在java中怎么强行终止一个线程的执行。
stop方法存在很大的缺点,容易丢失数据。因为这种方法是直接将线程杀死。
线程没有保存到数据将会丢失,不建议使用
 */
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();
        //模拟5s
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5s后强行终止t线程
        t.stop();//已过时,不建议使用
    }
}
class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

修改标记
package com.bjpowernode.thread;
/*
怎么合理的终止一个线程的执行。这种方式是很常用的
 */
public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunnable4 r = new MyRunnable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        //模拟5s
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止线程
        //想要什么时候终止t的执行,就什么时候把标记修改为false
        r.run = false;
    }
}
class MyRunnable4 implements Runnable{
    boolean run = true;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (run){
                System.out.println(Thread.currentThread().getName()+"-->"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //终止当前线程
                return;
            }

        }
    }
}

线程调度(了解)

常见的线程调度模型

抢占式调度模型

哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些
java采用的就是抢占式调度模型

均分式调度模型

平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样
平均分配,一切平等
有一些编程语言,线程调度模型采用的是这种方式

线程调度的方法

实例方法:
void setPriority(int newPriority)设置线程的优先级
int getPriority() 获取线程的优先级
最低优先级1
默认优先级是5
最高优先级是10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的)
静态方法:
static void yeild() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其他线程使用。
yeild()方法的执行会让当前线程从“运行状态”回到“就绪状态”
注意:回到就绪状态之后,可能还会再次抢到。

线程安全

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。

什么时候数据在多线程并发的环境下会存在安全问题

1.多线程并发
2.有共享数据
3.共享数据有修改的行为

怎么解决线程安全问题(前导)

在多线程并发的环境下,有共享数据,而且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行(不能并发)
用排队执行解决线程安全问题。
这种机制被称为:线程同步机制

线程同步

两种模型:
1.异步编程模型
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型
其实就是:多线程并发(效率较高)
2.同步编程模型
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程直接发生了等待关系,这就是同步编程模型
同步就是排队

线程同步的语法

线程同步机制的语法是:
synchronized(){
//线程同步代码块
}
synchronized后面小括号中传的这个“数据”是相当关键的。
这个数据必须是多线程共享的数据。才能达到多线程排队。
()中写什么?
那要看你想要哪些线程同步。
假设t1,t2,t3,t4,t5,有5个线程
你只希望前三个排队,t4,t5不需要排队,怎么办?
你一定要在()中写一个t1,t2,t3共享的对象,
而这个对象对于t4,t5来说是不共享的

synchronized的三种写法
第一种
         /*以下代码的执行原理:
            1.假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
            2.假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
            找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
            占有这把锁的。直到同步代码块代码结束,这把锁才会释放
            3.假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象
            的这把锁,结果这把锁已经被t1占有,t2只能在同步代码块外面等待t1的结束,
            直到t1把同步代码块执行结束,t1会归还这把锁,此时t2终于等到这把锁,然后
            t2占有这把锁之后,进入同步代码块执行程序.*/
 synchronized (this){
        double before = this.getBalance();
        double after = before - money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }
第二种
/*在实例方法上也可以使用synchronized。
synchronized出现在实例方法上,
缺点:一定锁的是this,不能是其他对象了,所以这种方式不灵活。
另外当它出现在实例方法上时,表示整个方法体都需要同步,可能会无故扩大同
步的范围,导致程序的执行效率降低。所以这种方式不常用。
优点:代码简洁,如果共享的对象就是this,并且需要同步的代码块是整个方法
体,建议使用这种方式*/
    public synchronized void withdraw(double money){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            this.setBalance(after);
    }
第三种

在静态方法上使用synchronized
表示找类锁,类锁永远只有一把。
可以用来保护静态变量的安全

对象锁:1个对象1把锁,100个对象100把锁
类锁:100个对象,也可能只是1把类锁

TjJtMR.png

Java中三大变量哪个不存在线程安全问题?

实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
以上三大变量中:
局部变量永远都不会存在线程安全问题
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享
(实例变量和静态变量都属于成员变量)
思考题:

局部变量中用StringBuffer还是StringBuilder

答:用StringBuilder。因为局部变量中不存在线程安全问题。StringBuffer是线程安全的,降低了效率(java.lang.StringBuffer中有的方法中使用了synchronized)。StringBuffer不是线程安全的,但效率高

另外ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的

看4道synchronized的面试题
package com.bjpowernode.exam1;
//面试题1:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为doOther方法没有synchronized
//面试题2:在doOther方法上加入synchronized后,doOther方法
//       执行的时候需要等待doSome方法的结束吗?
//需要
public class Exam01 {
    public static void main(String[] args) throws
    InterruptedException {
        MyClass mc = new MyClass();
        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000);
        t2.start();

    }
}
class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc=mc;
    }
    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }

    }
}
class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
package com.bjpowernode.exam3;
//面试题3:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为MyClass对象是两个,两把锁

public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();
        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000);
        t2.start();

    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc=mc;
    }
    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }

    }
}

class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

package com.bjpowernode.exam4;
//面试题4:doOther方法执行的时候需要等待doSome方法的结束吗?
//需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。

public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();
        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000);
        t2.start();

    }
}

class MyThread extends Thread{
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc=mc;
    }
    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }

    }
}

class MyClass{
    //synchronized出现在静态方法上是找类锁
    public synchronized static void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized static void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

死锁
package com.bjpowernode.deadlock;
/*
写一个死锁。
synchronized最好不要嵌套使用,一不小心可能造成死锁
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        //t1和t2两个线程共享o1,o2
        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);

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

    }
}
class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }

    }
}
class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }

    }

}

开发中怎么解决线程安全问题(总结)

并不是一上来就选择线程同步(synchronized)的
synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量(并发量)降低,用户体验差。在不得已的情况下再选择线程同步机制。

  • 方案1:尽量使用局部变量代替实例变量和静态变量
  • 方案2:如果必须是实例变量,那么可以考虑创建多个对象,这样实例对象的内存就不共享了(1个线程对应1个对象,100个线程对应100个对象,对象不共享就没有数据安全问题了。)
  • 方案3: 如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择线程同步机制(synchronized)了

守护线程

java语言中线程分为两大类:
一类是:用户线程
另一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)

守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程

package com.bjpowernode.thread;
/*
守护线程
 */
public class ThreadTest14 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();
        t.setName("备份数据的线程");
        //启动线程之前,将线程设置为守护线程
        t.setDaemon(true);
        t.start();

        //主线程:主线程是用户线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
class BakDataThread extends Thread{
    @Override
    public void run() {
        int i = 0;
        //即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止
        while (true){
            System.out.println(Thread.currentThread().getName()+"--->"+(++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

定时器

定时器的作用:间隔特定的时间,执行特定的程序

在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:

  • 可以使用sleep方法,睡眠,设置睡眠时间,这是最原始的定时器。
  • 在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
  • 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
package com.bjpowernode.thread;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.SimpleFormatter;

public class TimerTest {
    public static void main(String[] args) throws Exception {
        //创建定时器对象
        Timer timer = new Timer();
        //守护线程的方式
        //Timer timer = new Timer(true);

        //指定定时任务
        SimpleDateFormat sdf = new SimpleDateFormat
        ("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2022-01-06 15:55:15");
        //由于TimerTask是个抽象类,不能直接new对象所以可以用匿名
        //内部类方式
//        timer.schedule(new TimerTask() {
//            @Override
//            public void run() {
//         SimpleDateFormat sdf = new //SimpleDateFormat
//         ("yyyy-MM-dd HH:mm:ss");
//                String strTime = sdf.format(new Date());
//                System.out.println(strTime+":成功完成了一次
//                数据备份!");
//            }
//        },firstTime,1000*10);
        timer.schedule(new LogTimerTask(),firstTime,1000*10);
    }
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{

    @Override
    public void run() {
        //编写你需要执行的任务就行了。
        SimpleDateFormat sdf = new SimpleDateFormat
        ("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime+":成功完成了一次数据备份!");
    }
}


实现线程的第三种方式

实现callable接口。(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。

思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么才能拿到这个执行结果呢?
使用第三种方式:实现Callable接口方式

package com.bjpowernode.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/*
实现线程的第三种方式:
    实现Callable接口
    这种方式的优点:可以获取到线程的执行结果。
    这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程
    受堵塞,效率较低
 */
public class ThreadTest15 {
    public static void main(String[] args) throws Exception {
        //第一步:创建一个“未来任务类”对象
        //参数非常重要,需要给一个Callable接口实现类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                //call方法相当于run方法,不过call有返回值
                //线程执行一个任务,执行之后可能会有一个执行结果
                //模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000*10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a+b;  //自动装箱(300结果变成Integer)
            }
        });

        //创建线程对象
        Thread t = new Thread(task);
        //启动线程
        t.start();
        //这里是main方法,是在主线程中
        //在主线程中,怎么获取t线程的返回结果?
        //get()方法的执行会导致“当前线程堵塞”
        Object obj = task.get();
        System.out.println("线程执行结果:"+obj);

        //main方法这里的程序要想执行必须等待get()方法的结束
        //而get()方法可能需要很久。因为get()方法是拿另一个线程的
        //执行结果,另一个线程执行是需要时间的。
        System.out.println("Hello World!");
    }
}

Object类中的wait和notify方法(生产者和消费者模式)

wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的。

wait方法的作用
Object o = new Object();
o.wait();

表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。

notify方法作用
Object o = new Object();
o.notify();

表示:唤醒正在o对象上等待的线程
还有一个notifyAll方法:唤醒o对象上处于等待的所有线程

package com.bjpowernode.thread;

import java.util.ArrayList;
import java.util.List;

/*
1.使用wait方法和notify方法实现”生产者和消费者模式“
2.什么是生产者和消费者模式?
    生产线程负责生产,消费线程负责消费
    生产线程和消费线程要达到均衡
    这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法

3.wait和notify方法不是线程对象的方法,是普通java对象都有的方法
4.wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库,有线程安全问题
5.wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁
6.notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
7.模拟这样一个需求:
    仓库我们采用List集合。
    List集合中假设只能存储1个元素
    1个元素就表示仓库满了
 */
public class ThreadTest16 {
    public static void main(String[] args) {
        //创建一个仓库对象,共享的
        List list = new ArrayList();
        //创建两个线程对象
        //生产者线程
        Thread t1 = new Thread(new Prodecer(list));
        //消费者线程
        Thread t2 = new Thread(new Consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");

        t1.start();
        t2.start();
    }
}
//生产线程
class Prodecer implements Runnable{
    //仓库
    private List list;
    public Prodecer(List list){
        this.list=list;
    }
    @Override
    public void run() {
        //一直生产
        while (true){
            //给仓库对象List加锁
            synchronized (list){
                if (list.size()>0){//大于0.说明仓库中已经有1个元素了
                    //当前线程进入等待状态,并且释放Producer之前占有的List集合的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //程序能够执行到这里说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName()+"--->"+obj);
                //唤醒消费者消费
                list.notify();
            }
        }
    }
}
//消费线程
class Consumer implements Runnable{
    //仓库
    private List list;
    public Consumer(List list){
        this.list=list;
    }
    @Override
    public void run() {
        //一直消费
        while (true){
            synchronized (list){
                if (list.size()==0){
                    //仓库已经空了
                    //消费者线程等待,释放掉List集合的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //程序能够执行到此处说明仓库中有数据,进行消费。
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName()+"--->"+obj);
                //唤醒生产者生产
                list.notify();
            }

        }


    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值