目录
实现线程的第三种方式 :实现Callable接口(JDK8新特性)
多线程概述
1.什么是线程? 什么是进程?
进程是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程
2.对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后
会先启动JVM,而JVM就是一个进程
JVM再启动一个主线程调用main方法
同时再启动一个垃圾回收线程负责看护,回收垃圾.
最起码,现在的java程序中至少两个线程并发。
一个是垃圾回收线程,一个是执行main方法的主线程.
进程和线程的关系
进程可以看做是现实生活当中的公司,线程可以看做是公司当中的某个员工。
注意:
进程A和B的内存独立不共享.
永劫无间是一个进程
原神是一个进程
这两个进程是独立的,不共享资源
线程A和B:
在java语言中,线程A和线程B,堆内存和方法区内存共享.
但是栈内存独立,一个线程一个栈
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互补干扰,各自执行各的,这就是多线程并发.
java中之所以有多线程机制,目的就是为了提高程序的处理效率.
多线程并发的理解
什么是真正的多线程并发?
t1线程执行t1的
t2线程执行t2的
t1不会影响t2,t2也不会影响t1,这叫做真正的多线程并发.
对于多核的CPU电脑来说,真正的多线程并发是没问题的.
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种"多线程并发”的感觉.
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,
多个线程直接频繁切换执行,会给人一种错觉:貌似多个事情同时在做
分析程序存在几个线程
package thread1;
public class ThreadTest01 {
public static void main(String[] args) {
System.out.println("main begin");
m1();
System.out.println("main over");
}
private static void m1() {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
private static void m2() {
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
private static void m3() {
System.out.println("m3 execute!");
}
}
实现线程的第一种方式
java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了.
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法
package thread1;
/*
实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法.
*/
public class ThreadTest02 {
public static void main(String[] args) {
//新建一个分支线程对象
MyThread myThread=new MyThread();
//启动线程
//t.run; //不会启动线程,不会分配新的分支栈。(这种方式就是单线程)
//start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了,这段代码的任务只是为了
//开辟一个栈空间,只要新的栈空间开出来了,start()方法就结束了,线程就启动成功了.
//启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的.
myThread.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);
}
}
}
实现线程的第二种方式
package thread1;
/*
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
*/
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<1000;i++)
{
System.out.println("主线程--->"+i);
}
}
}
//这并不是一个线程类,是一个可运行的类,它还不是一个线程
class MyRunnable implements Runnable
{
@Override
public void run() {
for(int i=0;i<1000;i++)
{
System.out.println("分支线程--->"+i);
}
}
}
注意:第二种方式实现接口比较常用,因为一个类实现了接口还可以继续去继承其他的类,更灵活
采用匿名内部类的方式
package thread1;
public class ThreadTest04 {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类的形式
//这是通过一个没有名字的类,new 出来的对象
Thread t=new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000;i++)
{
System.out.println("t线程--->"+i);
}
}
});
//启动线程
t.start();
for(int i=0;i<1000;i++)
{
System.out.println("main线程--->"+i);
}
}
}
线程生命周期
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
获取线程的名字
package thread1;
/*
1.怎么获取当前线程对象?
2.获取线程对象的名字
3.修改线程对象的名字
*/
public class ThreadTest05 {
public static void main(String[] args) {
//创建线程对象
MyThread2 t=new MyThread2();
//设置线程的名字
// t.setName("tt");
//获取线程的名字
String tname=t.getName();
System.out.println(tname);//tt
MyThread2 t2=new MyThread2();
System.out.println(t2.getName());
//启动线程
t.start();
}
}
class MyThread2 extends Thread
{
@Override
public void run() {
for (int i=0;i<100;i++)
{
System.out.println("分支线程--->"+i);
}
}
}
获取当前线程对象
package thread1;
/*
1.怎么获取当前线程对象?
2.获取线程对象的名字
3.修改线程对象的名字
*/
public class ThreadTest05 {
public static void main(String[] args) {
//currentThread就是当前线程对象
//这个代码出现在main方法当中,所以当前线程就是主线程
Thread currentThread=Thread.currentThread();
System.out.println(currentThread);
//创建线程对象
MyThread2 t=new MyThread2();
//设置线程的名字
// t.setName("tt");
//获取线程的名字
String tname=t.getName();
System.out.println(tname);//tt
MyThread2 t2=new MyThread2();
System.out.println(t2.getName());
//启动线程
t.start();
t2.start();
}
}
class MyThread2 extends Thread
{
@Override
public void run() {
for (int i=0;i<100;i++)
{
//currentThread就是当前线程对象,就是t.start()的t
Thread currentThread=Thread.currentThread();
System.out.println(currentThread.getName()+"--->"+i);
}
}
}
线程的sleep方法
package thread1;
/*
关于线程的sleep()方法:
static void sleep(long millis)
1.静态方法:Thread.sleep(1000);
2.参数是毫秒
3.作用:让当前线程进入休眠,进入"阻塞状态",放弃占有CPU时间片,让给其它线程使用。
出现在A线程中,A线程就会进入休眠
*/
public class ThreadTest06 {
public static void main(String[] args) {
//让当前线程进入休眠5s
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后执行这里的代码
System.out.println("hello,world!");
}
}
sleep方法的相关面试题
package thread1;
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();
}
//5秒之后这里才会执行
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 thread1;
/*
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么终止/叫醒一个正在睡眠的线程
注意:这个不是中断线程的执行,而是中断线程的睡眠
*/
public class ThreadTest08 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable2());
t.setName("t");
t.start();
//希望5s之后,t线程醒来
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
//打印异常信息
e.printStackTrace();
}
// 中断t线程的睡眠 (中断睡眠的方式依靠了java的异常处理机制)
t.interrupt();// 干扰,一盆冷水过去!
}
}
class MyRunnable2 implements Runnable{
@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");
}
}
强制终止线程的执行
package thread1;
public class ThreadTest10 {
public static void main(String[] args) {
MyRunable4 r=new MyRunable4();
Thread t=new Thread(r);
t.setName("t");
t.start();
//模拟5s
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
r.run=false;
}
}
class MyRunable4 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;
}
}
}
}
线程调度概述
1.常见的线程调度模型有哪些?
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。
均分式调度模型:
平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样。平均分配,
一切平等。
有一些编程语言,线程调度模型采用的是这种方式。
线程调度的方法
实例方法:
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程的优先级
最低优先级1
默认优先级5
最高优先级10
静态方法:
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用.
yield()方法的执行会让当前线程从"运行状态"回到"就绪状态"
实例方法:
void join()
合并线程
class MyThread1 extends Thread{
public void doSome()
{
MyThread2 t=new MtThread2();
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才可以执行
}
}
class MyThread2 extends Thread{
}
package thread1;
/*
关于线程的优先级
*/
public class ThreadTest11 {
public static void main(String[] args) {
System.out.println("最高优先级 "+Thread.MAX_PRIORITY);
System.out.println("最低优先级 "+Thread.MIN_PRIORITY);
System.out.println("默认优先级 "+Thread.NORM_PRIORITY);
//获取当前线程对象,获取当前线程的优先级
Thread currentThread=Thread.currentThread();
//main默认优先级:5
// System.out.println(currentThread.getName()+"线程的默认优先级是 :"+currentThread.getPriority());
Thread t=new Thread(new MyRunnable5());
t.setPriority(10);
t.setName("t");
t.start();
//优先级较高的,抢到CPU的时间相对多一些(处于运行状态占的时间多一些)
for (int i=0;i<1000;i++)
{
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class MyRunnable5 implements Runnable{
@Override
public void run() {
for (int i=0;i<1000;i++) {
System.out.println(Thread.currentThread().getName() + "---->" + i);
}
}
}
线程让位
package thread1;
/*
让位,当前线程暂停,回到就绪状态,让给其他线程
静态方法:Thread.yield();
*/
public class ThreadTest12 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable6());
t.setName("t");
t.start();
for (int i=1;i<=10000;i++)
{
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class MyRunnable6 implements Runnable{
@Override
public void run() {
for (int i=1;i<=10000;i++)
{
if (i%100==0)
{
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
线程合并
package thread1;
/*
线程合并:
*/
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
Thread t=new Thread(new MyRunnable7());
t.setName("t");
t.start();
//合并线程
try {
t.join();//t 合并到当前线程中,当前线程受阻塞,t线程执行直到结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable7 implements Runnable
{
@Override
public void run() {
for (int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
在内存上线程合并并不是意味着两个栈合并了,而是协调关系。
当前线程阻塞,t线程执行,t线程执行完了当前线程才可以执行.
线程安全
什么时候数据在多线程并发的环境下会存在安全问题?
条件1:多线程并发
条件2: 有共享数据
条件3: 共享数据有修改行为
满足上述三个条件后,就会存在线程安全问题
怎么解决线程安全问题?
线程排队执行(不能并发)(线程同步机制)
线程同步:线程不能并发了,需要排队执行,尽管排队执行会牺牲一部分效率,但是安全第一
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高)
异步--并发
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候, 必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型.效率较低,线程排队
执行。
同步--排队
Account类
package bean;
import static java.lang.Thread.sleep;
/*
银行账户
*/
public class Account {
private String actno;//账号
private double balance;//余额
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public void withdraw(double money)
{
//t1和t2并发这个方法,(t1和t2是两个栈,两个栈操作堆中同一个对象)
//取款之前的余额
double before=this.getBalance();
//取款之后的余额
double after=before-money;
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
this.setBalance(after);
}
}
AccountThread类
package bean;
public class AccountThread extends Thread{
//两个线程必须共享同一个账户对象
private Account act;
//通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act=act;
}
public void run()
{
// run方法的执行表示取款操作
//假设取款5000
double money=5000;
//取款
act.withdraw(money);
System.out.println(Thread.currentThread().getName()+"对账户"+act.getActno()+"取款成功,余额"+act.getBalance());
}
}
测试类
package bean;
public class ThreadTest14 {
public static void main(String[] args) {
// 创建账户对象(只创建1个)
Account act=new Account("act-01",10000);
// 创建2个线程
Thread t1=new AccountThread(act);
Thread t2=new AccountThread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
存在问题,2个人都取5000,余额应该为0;
同步代码块synchronized
import static java.lang.Thread.sleep;
/*
银行账户
*/
public class Account {
private String actno;//账号
private double balance;//余额
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public void withdraw(double money)
{
// 以下这几行代码必须是线程排队的,不能并发
// 一个线程把这里的代码全部执行结束后,另一个线程才能进来
/*
线程同步机制的语法:
synchronized()
{
//线程同步代码块
}
synchronized后面小括号中传的这个"数据"是相当关键的.
这个数据必须是多线程共享的数据,才能达到多线程排队
()中写什么?
那要看你想让哪些线程同步
假设t1、t2、t3、t4、t5 5个线程
你只希望t1 t2 t3排队, t4 t5不需要排队。怎么办?
你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于 t4 t5 来说不是共享的。
这里的共享对象是: 账户对象
账户对象是共享的,那么this 就是账户对象吧!!!
不一定是this,这里这要是多线程共享的那个对象就行.
在java语言中,任何一个对象都有"一把锁",其实这把锁就是标记(只是把它叫做锁。)
100个对象100把锁,1个对象一把。
一下代码的执行原理:
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 {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
this.setBalance(after);
}
}
}
synchronized()括号里面只要是t1和t2共享的对象就行,比如 括号里填obj也可以
当然,若是填"abc",则所有的对象都共享了,一个人取款,要让全天下的人都等待,应该让机制变成只有对应账户A的人取款才需要等待正在取款账户A的人操作完毕再取款。
哪些变量具有线程安全问题
java中有三大变量
实例变量:在堆中
静态变量: 在方法区
局部变量: 在栈中
以上三大变量中:
局部变量永远都不会存在线程安全问题
因为局部变量不共享(一个线程一个栈)
实例变量在堆中,堆只有一个.
静态变量在方法区,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
如果使用局部变量的话,建议使用:StringBuilder.
因为局部变量不存在线程安全问题,选择StringBuilder,StringBuffer效率比较低。
synchronized出现在实例方法上
这个时候synchronized锁的一定是this
如果共享的对象是this,并且需要同步的代码块是整个方法体,
建议使用这种方式
synchronized的三种写法
第一种:同步代码块
灵活
synchronized(线程共享对象)
{
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方在法体
第三种:在静态方法上使用synchronized
表示找类锁
类锁永远只有一把
就算创建了100个对象,类锁也只有一把。
对象锁:1个对象一把
synchronized相关面试题
package bean;
/*
doOther()方法的执行需要等待doSome()方法的结束吗?
不需要,它不是synchronized修饰的
*/
public class Exaxm01 {
public static void main(String[] args) {
Myclass mc=new Myclass();
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//这个睡眠的作用是为了保证t1先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
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");
}
}
doOther()方法前面加上 synchronized 需要等待
new两个Myclass对象(两把锁)不需要等待
synchronizedc搞静态方法(类锁)需要等待
死锁概述
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)
{
}
}
}
}
发生死锁,10年后还是这样
开发中如何解决线程安全问题
一上来就用synchronized让线程同步吗?
不是,synchronized会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用 局部变量 代替 实例变量 和 静态变量 。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了.
(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized,线程同步机制。
守护线程概述
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程.
守护线程用在什么地方?
每天00:00的时候系统数据自动备份
这个需要使用到定时器,并且我们可以将定时器设置为守护线程
一直在那里看着,每次到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
实现守护线程
package bean;
public class ThreadTest15 {
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();
}
}
}
}
主线程结束后,备份数据的线程也跟着结束
实现定时器
定时器的作用:间隔特定的时间,执行特定的程序。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) throws Exception{
//创建定时器对象
Timer timer=new Timer();
//Timer timer=new Timer(true);//守护线程方式
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次)
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstime=sdf.parse("2022-12-10 00:16:00");
timer.schedule(new logTimerTask(),firstime,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。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest16 {
public static void main(String[] args) throws Exception{
//第一步:创建一个"未来任务类"对象
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call()方法相当于run()方法.只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
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();
//怎么在主线程中获取t线程的返回结果
Object obj=task.get();
System.out.println("线程执行结果:"+obj);
//main方法这里的程序要想执行,必须等待get()方法的结束
//而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果
//另一个线程执行是需要时间的.
System.out.println("hello world!");
}
}
还是两个栈,java虚拟机调度
这种方式的优点:可以获取线程的执行结果
这种方式的缺点: 效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低.
以上是今天的视频笔记。学习如逆水行舟,不进则退。