多线程:
1. 介绍:
-
什么是进程,什么是线程:
进程是一个应用程序,线程是一个进程中的执行场景或执行单元,一个进程可以启动多个线程。java中之所以有多线程机制,目的是为了提高程序的处理效率。
-
在java语言中,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。
-
使用了多线程机制之后,main方法结束,程序可能也不会结束。main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
2. 创建线程的方式:
-
在java语言中,实现线程一般有两种方式:
-
第一种方式:
-
编写一个类,直接继承java.lang.Thread,重写run()方法。
-
创建分线程对象,调用start()方法。
-
start()方法作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
-
启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)。run方法在分支栈的栈底部,main方法在主线程的栈底部。run和main是平级的。
-
//定义线程类 public class MyThread extends Thread{ //重写run方法 public void run(){ } } //创建线程对象 MyThread t=new MyThread(); //启动线程 t.start();
-
-
第二种方式:
-
编写一个类,实现java.lang.Runnable接口,实现run方法。比较常用。
-
//定义线程类 public class RunnableTest implements Runnable{ //重写run方法 public void run(){ } } //创建一个可运行的对象 RunnableTest rt=new RunnableTest(); //将一个可运行的对象封装成一个线程对象 Thread th=new Thread(rt); //启动线程 th.start();
-
-
采用匿名内部类方式:
public class ThreadTest04 { public static void main(String[] args) { //采用匿名内部类方式,创建线程对象 Thread t=new Thread(new Runnable() { @Override public void run() { for (int i=1;i<101;i++){ System.out.println("分支线程-->"+i); } } }); //启动线程 t.start(); for (int i=1;i<101;i++){ System.out.println("main-->"+i); } } }
-
3. 线程的生命周期图:

4. 线程的常见方法:
1. 获取当前线程对象
void setName(String name); //修改线程名字
String getName() ; //获取线程名称,默认为Thread-0
static Thread currentThread(); //返回当前线程对象
-
代码演示:
public class ThreadTest05 { public static void main(String[] args) { //创建线程对象 MyThread2 t1=new MyThread2(); // t.setName("xiaoma"); //获取线程的名字 String tName=t1.getName(); System.out.println(tName); MyThread2 t2=new MyThread2(); System.out.println(t2.getName()); //启动线程 t1.start(); System.out.println(Thread.currentThread().getName()); } } class MyThread2 extends Thread{ @Override public void run() { for (int i=0;i<100;i++){ //当t1线程执行run方法,那么当前线程就是t1 //当t2线程执行run方法,那么当前线程就是t2 Thread currentThread=Thread.currentThread(); System.out.println(currentThread.getName()+"---->"+i); //Thread-0--->1 } } }
2 . sleep()方法:
-
关于线程的sleep()方法:
//1. 静态方法: Thread.sleep(1000); //2. 参数是毫秒 //3. 作用:让当前线程进入休眠状态,进入“堵塞状态”,放弃占用CPU时间片,让给其他线程使用 //4. Thread.sleep()方法,可以做到间隔特定的时间,去执行一段特定的代码,每隔多久执行一次 static void sleep(long millis); -
代码演示:
/* 关于线程的sleep()方法 * */ public class ThreadTest06 { public static void main(String[] args) { /* try { //让当前线程进入休眠,睡眠5秒 Thread.sleep(1000 *5); } catch (InterruptedException e) { e.printStackTrace(); } //5秒之后执行这里代码 System.out.println("xiaoma");*/ for(int i=0;i<10;i++){ //每隔一秒,输出一次 System.out.println(Thread.currentThread().getName()+"--->"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
1. 关于sleep()方法的面试题:
-
问题:这行代码会让线程t进入休眠状态嘛?
不会,sleep()是静态方法和t线程没关系,在执行的时候还是会转为Thread.sleep(1000*5)。
这行代码的作用是:让当前线程进入休眠,也就是说让main线程进入休眠。
public class ThreadTest07 {
public static void main(String[] args) {
//创建线程对象
Thread t=new MyThread3();
t.setName("t");
t.start();
//调用sleep()方法
try {
//问题:这行代码会让线程t进入休眠状态嘛? 不会
//sleep()是静态方法和t线程没关系
// 在执行的时候还是会转为Thread.sleep(1000*5)
//这行代码的作用是:让当前线程进入休眠,也就是说让main线程进入休眠
t.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后这里才会执行
System.out.println("hello world");
}
}
class MyThread3 extends Thread{
public void run(){
for (int i=0;i<10000;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
3. 唤醒线程 interrupted():
-
void interrupt() //依靠了java的异常处理机制,终断睡眠,会打印出异常信息 -
代码演示:
/* sleep睡眠太久了,如何唤醒一个正在睡眠的线程。 * */ public class ThreadTest08 { public static void main(String[] args) { Thread t=new Thread(new MyRunnable2()); t.setName("t"); t.start(); //希望5秒之后t线程醒来 try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } //中断t线程的线程(这种终断睡眠的方式,依靠了java的异常处理机制。) t.interrupt(); //干扰 } } class MyRunnable2 implements Runnable{ //重点:run()当中的异常不能throws ,只能try catch //因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常 @Override public void run() { System.out.println(Thread.currentThread().getName()+"---> begin"); try { // 睡眠1年 Thread.sleep(1000*60*60*24*365); } catch (InterruptedException e) { //打印异常信息 e.printStackTrace(); } //1年之后才执行这个 System.out.println(Thread.currentThread().getName()+"---> end"); } }
4. 终止线程:
1. 第一种方式:
使用stop()方法:已过时,不建议使用,容易丢失数据。因为这种方式是直接将线程杀死,线程没有保存的数据会丢失。
-
代码演示:
public class ThreadTest09 { public static void main(String[] args) { Thread t=new Thread(new MyRunnable3()); t.setName("t"); t.start(); //模拟5秒睡眠 try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } //5秒之后强行终止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(); } } } }
2. 第二种方式:
在自定义的线程类中打一个boolean标记,想什么时候终止t的执行,就时候把标记修改为false。这种方式是很常用的。
-
代码演示:
/* 如何合理的终止一个线程的执行,这种方式是很常用的 * */ public class ThreadTest10 { public static void main(String[] args) { MyRunnable4 r= new MyRunnable4(); Thread t=new Thread(r); t.setName("t"); t.start(); //模拟5秒 try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } //终止线程 //想什么时候终止t的执行,就什么时候把标记修改为false,就行了 r.run=false; } } class MyRunnable4 implements Runnable{ //打一个boolean标记 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就结束了,你在结束之前还有什么没保存的,在这里可以保存 //save.... //终止线程执行 return; } } } }
5. 线程的调度:
-
常见的线程模型
- 抢占式调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些。java采用的就是抢占式调度模型。
- 均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样,平均分配,一切平等。
-
java中提供哪些方法和线程调度有关:
int getPriority(); //获取线程优先级 void setPriority(int newPriority); //设置线程优先级 static void yield() ; // 暂停当前正在执行的线程对象,并执行其他线程。 void join() ; //合并线程(当前线程进入堵塞,等待调用的线程执行完)注意:线程最低优先级是1,默认优先级是5,最高优先级是10。
yield()方法不是堵塞方法。让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状 态,有可能再抢到CPU时间片。
-
join()方法演示:
class MyThread1 extends Thread{ public void doSome(){ MyThread2 t=new MyThread2(); t.join(); //当前线程进入堵塞,t线程执行,直到t线程结束,当前线程才可以执行。 } } class MyThread2 extends Thread{ }
6. 线程安全(重点):
-
为什么这个是重点:
以后在开发中,我们的项目都是运行在服务器当中的,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了,这些代码我们都不需要编写。你要知道的是,你编写的代码需要放到一个多线程的环境下运行,你更需要关注的是,这些数据在多线程并发的环境下,是否是安全的。
-
什么时候数据在多线程并发的环境下会存在安全问题:

存在安全问题的三个条件:
- 多线程并发。
- 有共享数据。
- 共享数据有修改的行为。
-
如何解决线程安全问题:
**线程排队执行,不能并发。用排队执行解决线程安全问题,这种机制被称为线程同步机制。**线程同步会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。
-
说到线程同步这块,就涉及到两个专业术语:
- 异步编程模型:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1。谁也不需要等谁,这种模型称为异步编程模型。其实就是多线程并发,效率较高。
- 同步编程模型:线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型,效率低,线程排队执行。
1. 代码演示两个线程对同一个账户取款:
- 不使用线程同步机制,多线程对同一个账户取款,出现线程安全问题:
/*
* 银行账户
* */
public class Account {
//账户
private String actno;
//余额
private BigDecimal balance;
public Account() {
}
public Account(String actno, BigDecimal balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
//取款方法
public void withdraw(BigDecimal money){
// t1和t2并发这个方法....(t1和t2两个栈,两个栈操作堆中同一个对象。)
//取款之前的余额
BigDecimal before=this.getBalance();
//取款之后的余额
BigDecimal after=before.subtract(money);
// 在这里模拟一下网络延迟,100%会出现问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
//假设t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
this.setBalance(after);
}
}
/*
线程类
*/
public class AccountThread extends Thread{
// 两个线程必须共享同一个账户对象
private Account act;
//通过构造方法传递过来账户对象
public AccountThread(Account act){
this.act=act;
}
public void run(){
//run方法的执行表示取款操作
//假设取款5000
BigDecimal money=new BigDecimal(5000);
//取款
//多线程并发执行这个方法
act.withdraw(money);
System.out.println(Thread.currentThread().getName()+"对"+"账户"+act.getActno()+"取款成功,余额"+act.getBalance());
}
}
/*
测试类
*/
public class Test {
public static void main(String[] args) {
//创建账户对象
Account act=new Account("act-001",new BigDecimal(10000));
//创建两个线程
Thread t1=new AccountThread(act);
Thread t2=new AccountThread(act);
//设置线程name
t1.setName("t1");
t2.setName("t2");
//启动线程取款
t1.start();
t2.start();
}
}
如图:运行结果出问题,都取款5000,余额还剩5000:

2.代码改进(synchronized):
-
想不出现上面代码问题,以下这几行代码必须是线程排队的,不能并发,一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
-
线程同步机制的语法是:
synchronized(){ //线程同步代码块。 } -
注意:synchronized后面小括号中传的这个 “数据”是相当关键的。这个数据必须是多线程共享的数据,才能达到多线程排队。
-
()中写什么?假设t1,t2,t3,t4,t5有5个线程,你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办
那要看你想让哪些线程同步。你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
-
这里的共享对象是:账户对象。那么this就是账户对象。
-
改进Account类的withdraw()方法,代码如下:
//取款方法 public void withdraw(BigDecimal money){ //以下这几行代码必须是线程排队的,不能并发 //一个线程把这里的代码全部执行结束之后,另一个线程才能进来。 /* * 线程同步机制的语法是: * synchronized(){ * //线程同步代码块。 * } * * synchronized后面小括号中传的这个 “数据”是相当关键的。 * 这个数据必须是多线程共享的数据,才能达到多线程排队。 * * ()中写什么? * 那要看你想让哪些线程同步。假设t1,t2,t3,t4,t5有5个线程 * 你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办 * 你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。 * * 这里的共享对象是:账户对象。 * 账户对象是共享的,那么this就是账户对象。 * 这里不一定是this,这里只要是多线程共享的那个对象就行。 * */ synchronized (this){ //取款之前的余额 BigDecimal before=this.getBalance(); //取款之后的余额 BigDecimal after=before.subtract(money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //更新余额 this.setBalance(after); } } -
synchronized是如何保证线程安全的:
在java语言中,任何一个对象都有 “一把锁”,其实这把锁就是一个标记。(只是把它叫做锁),100个对象,100把锁,1个对象,1把锁。
-
上面代码的执行原理:
假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。若t1先执行了,遇到synchronized,这个时候自动找 "后面共享对象"的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中t1一直都是占有这把锁的。t2此时都是在同步代码块外面等待t1的。直到同步代码块代码结束,t1才会归还这把锁。此时t2才会获得这把锁,然后t2占有这把锁之后,才进入同步代码块,执行程序。
这样就达到了线程排队执行。要注意的是这个共享对象一定要选好,这个共享对象一定是你需要排队执行的这些线程对象所共享的。
-
注意:共享对象中的实例变量obj也是共享的。注意不能是局部变量。若写字符串“abc”时(不可变,存在字符串常量池中,共享),可以,但现有线程都会共享。
如:
public class Account { //账户 private String actno; //余额 private BigDecimal balance; //对象 //实例变量(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的) Object obj=new Object(); .... // synchronized (this){ synchronized (obj){ //取款之前的余额 BigDecimal before=this.getBalance(); //取款之后的余额 BigDecimal after=before.subtract(money); ... -
java中有三大变量:
- 实例变量:在堆中。
- 静态变量:在方法区中。
- 局部变量:在栈中
以上三大变量中,局部变量永远都不会存在线程安全问题,因为局部变量在栈中,所有局部变量永远都不会共享。(一个线程一个栈)。实例变量在堆中,堆只有一个。静态变量在方法区中,方法区只有一个。堆和方法区都是多线程共享的。
总结:
-
局部变量+常量:不会有线程安全问题。
-
成员变量:可能会有线程安全问题。
-
可以在实例方法上使用synchronized,不常用。
缺点:
- synchronized出现在实例方法上,一定锁的是this,不能是其他的对象了,所以这种方式是不灵活的。
- synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。
优点:
- 节俭代码。如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这个方式。
... //可以在实例方法上使用synchronized,synchronized出现在实例方法上,一定锁的是this public synchronized void withdraw(BigDecimal money){ //取款之前的余额 BigDecimal before=this.getBalance(); //取款之后的余额 BigDecimal after=before.subtract(money); ..... -
如果使用局部变量的话,建议使用StringBuilder。因为局部变量不存在线程安全问题。选择StringBuffer效率比较低。
另外:ArrayList是非线程安全的,Vector是线程安全的。HashMap,HashSet是非线程安全的,Hashtable是线程安全的。
3.总结:
-
synchronized的三种写法:
-
同步代码块:灵活
synchronized(线程共享对象){ 同步代码块; } -
在实例方法上使用synchronized:需要共享对象一定是this,并且同步代码块是整个方法体。
public synchronized void withdraw(BigDecimal money){ 同步代码块; } -
在静态方法上使用synchronized:表示找类锁。类锁永远只有一把。就算创建了100个对象,类锁还是一把。保证静态变量安全
-
4. synchrozied面试题:
以下代码,doOther方法执行的时候需要等待doSome方法结束嘛?
- 不需要,因为doOther()方法没有synchronized,t2不需要等待。
/*
* 面试题: doOther方法执行的时候需要等待doSome方法结束嘛?
* 不需要,因为doOther()方法没有synchronized ,t2不需要等待。
* */
public class Exam01 {
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;
}
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 ,并且this指向同一个对象 mc,但mc锁被doSome()占用
....
class Myclass{
public synchronized void doSome(){
....
}
public synchronized void doSome(){
.....
}
}
.....
3.不需要,因为this指向的不是同一个对象,锁不同。
public class Exam01 {
public static void main(String[] args) {
Myclass mc1=new Myclass();
Myclass mc2=new Myclass();
Thread t1=new MyThread(mc1);
Thread t2=new MyThread(mc2);
....
}
}
....
class Myclass{
public synchronized void doSome(){
....
}
public synchronized void doSome(){
.....
}
}
.....
4.需要,因为synchronized出现在静态方法上是类锁,不管创建几个对象,类锁只有1把。
public class Exam01 {
public static void main(String[] args) {
Myclass mc1=new Myclass();
Myclass mc2=new Myclass();
Thread t1=new MyThread(mc1);
Thread t2=new MyThread(mc2);
....
}
}
....
class Myclass{
//synchronized出现在静态方法上是类锁
public synchronized static void doSome(){
....
}
public synchronized static void doSome(){
.....
}
}
.....
5. 死锁:
-
什么是死锁:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
-
代码实现:
/* * 死锁代码要会写 * * */ public class DeadLock { public static void main(String[] args) { Object o1=new Object(); Object o2=new Object(); //t1和t2两个线程共享o1,o2 Thread t1=new MyThread01(o1,o2); Thread t2=new MyThread02(o1,o2); t1.start(); t2.start(); } } class MyThread01 extends Thread{ Object o1; Object o2; public MyThread01(Object o1,Object o2){ this.o1=o1; this.o2=o2; } public void run(){ synchronized (o1){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2){ } } } } class MyThread02 extends Thread{ Object o1; Object o2; public MyThread02(Object o1,Object o2){ this.o1=o1; this.o2=o2; } public void run(){ synchronized (o2){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1){ } } } } -
我们在以后开发中应该怎么解决线程安全问题?
- 不是一上来就选择线程同步,synchrozied。因为synchrozied会让程序的执行效率降低,用户体验不好。系统的用户吞吐量减低。用户体验差,在不得已的情况下再选择线程同步机制。
- 第一种方案:尽量使用局部变量代替 “实例变量和静态变量”。
- 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象,对象不共享,就没有数据安全问题了。)
- 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchrozied。线程同步机制。
7. 守护线程:
-
概述:
java语言中线程分为两大类。一种是用户线程,一种是守护线程(后台线程)。其中具有代表性的就是垃圾回收线程(守护线程)。
-
守护线程特点**:一般守护线程是一个死循环**,所有的用户线程只要结束,守护线程自动结束。主线程main方法是一个用户线程。
-
守护一般用在什么地方:如每天00:00的时候,系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直去监听,每到00:00的时候就备份一次
-
如何设置线程为守护线程:
void setDaemon(boolean on) //boolean为true时,即该线程为守护线程
- 守护线程代码演示:线程t是main方法的守护线程,只要main方法一结束,就算线程t是一个死循环,t依旧会结束。
/*
* 守护线程
* */
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{
public void run(){
int i=0;
//即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动结束
while (true){
System.out.println(Thread.currentThread().getName()+"-->"+(++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
8. 定时器(重要):
-
定时器的作用:
间隔特定的时间,执行特定的程序。比如每周要进行银行账户的走账操作,每天要进行数据的备份操作。在实际的开发中,每隔多久执行一段特定的程序,这种需求很常见。在java中有多种方式实现。
- 可以使用sleep(), 设置睡眠时间,每到这个时间点醒来,执行任务,这种方式是最原始的定时器。(比较low)
- 在java类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
- 在实际的开发中,目前使用较多的是SprIng框架中提供的SpringTask框架,这个框架只需要进行简单的配置,就可以完成定时器的任务。
-
如何实现定时器:
//创建定时器对 Timer timer=new Timer(); //指定定时任务 //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次); //其中定时任务可写一个类继承TimerTask接口,或者使用匿名内部类的方式 -
代码如下
/*
* 使用定时器指定定时任务
* */
public class TimerTest {
public static void main(String[] args) throws ParseException {
//创建定时器对象
Timer timer=new Timer();
//Timer timer1=new Timer("ma",true);以守护线程的方式创建一个叫ma的定时器
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime=sdf.parse("2020-07-30 10:40:00");
//timer.schedule(new LogTimerTask(),firstTime,1000*10);
//采用匿名内部类的方式
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);
}
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
/*
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+":成功完成一次数据备份");
}
}*/
9. 实现线程的第三种方式:
-
创建一个FutureTask未来任务类对象,通过在Callable接口的实现类中重写call()方法,来实现多线程。这种实现的线程可以通过**get()**方法获取线程的返回值。之前的两种方法都是无法获取返回值的,因为run方法返回void。
-
优点:可以获取线程执行结果。
缺点:效率比较低,在获取t线程的执行结果的时候,当前线程会受堵塞。
-
代码演示:
/*
* 实现线程的第三种方式,实现Callable接口
* */
public class ThreadTest15 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1. 创建一个未来任务类对象。
// 参数非常重要,需要给一个Callable接口的实现类对象
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; //自动装箱
}
});
//创建线程对象
Thread t=new Thread(task);
//启动线程
t.start();
//在主线程中,如何获取t线程的返回结果。
//get()方法的执行会导致当前线程阻塞。
System.out.println("线程执行结果"+task.get());
//main方法这里的程序要想执行,必须等待get()方法结束
// 而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果
//另一个线程执行是需要时间的
System.out.println("hello world");
}
}
10. wait和notify方法:
-
概述:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的。wait方法和notify方法不是通过线程对象调用。
-
两个方法的作用:
-
wait()方法作用:
Object o=new Object(); o.wait(); //表示 让正在o对象上活动的线程进入无期限等待状态,直到被notify()或notifyAll()唤醒 -
notify()方法作用:唤醒正在等待的线程。
-
1. 生产者和消费者模式:
-
分析如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hEPE6CFP-1596362088799)(C:\Users\user\Desktop\java分析图\生产者和消费者.png)]
-
代码实现:
/* * 使用wait()方法和notify()方法实现生产者和消费者模式 * 1. 生产线程负责生产,消费线程负责消费,生产线程和消费线程要达到均衡这是一种特殊的业务需求 * 在这种特殊的情况下,需要使用wait()和notify()方法。 * 2. wait和notify不是线程对象的方法,是普通java对象都有的方法。 * 3. wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库, * 有线程安全问题。 * 4. wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放t对象之前占 * 有的o对象的锁 * 5. notify()作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前 * 占用的锁 * * 6. 模拟这样的需求: * 仓库我们采用list集合,List集合中假设只能存储1个元素。1个元素就表示仓库满了, * 如果List集合中元素个数是0,就表示仓库是空的。保证List集合中是最多存储1个元素。 * 必须做到这样效果:生产一个,消费一个 * */ public class ThreadTest16 { public static void main(String[] args) { //创建一个仓库对象,共享的 List list=new ArrayList(); //创建两个线程对象 //生产者线程 Thread t1=new Thread(new Producer(list)); //消费者线程 Thread t2=new Thread(new Consumer(list)); t1.setName("生产者线程"); t2.setName("消费者线程"); t1.start(); t2.start(); } } //生产线程 class Producer implements Runnable{ //仓库 private List list; public Producer(List list){ this.list=list; } @Override public void run() { //一直生产(使用死循环模拟一直生产) while (true){ //表示给仓库对象list加锁 synchronized (list){ if(list.size()>0){ //大于0 说明仓库已经有一个元素了,仓库已满 //当前线程进入等待状态,并且释放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){ //仓库已经空了 try { //仓库空了,消费者线程等待,释放lsit集合的锁 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //程序能够执行到此,说明仓库中有数据进行消费 Object obj=list.remove(0); System.out.println(Thread.currentThread().getName()+"消费了 --->"+obj); //唤醒生产者生产 list.notify(); } } } }
1. 作业:
- 创建两个线程,实现t1输出奇数,t2输出偶数,交替输出:
public class ThreadTest17 {
public static void main(String[] args) {
Num num=new Num();
Thread t1=new Thread(new ji(num));
Thread t2=new Thread(new ou(num));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class Num{
int i=1;
}
class ji implements Runnable{
private Num num ;
public ji(Num num){
this.num=num;
}
@Override
public void run() {
while (true){
synchronized (num){
if(num.i%2==0){
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->"+num.i);
num.i++;
num.notifyAll();
}
}
}
}
class ou implements Runnable{
private Num num;
public ou(Num num){
this.num=num;
}
@Override
public void run() {
while (true){
synchronized (num){
if(num.i%2!=0){
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->"+num.i);
num.i++;
num.notifyAll();
}
}
}
}
本文详细介绍了Java中的多线程概念,包括线程的创建、生命周期、常见方法如sleep()和interrupted(),以及线程调度、线程安全问题和解决方案,如synchronized关键字的使用。此外,还讨论了守护线程、定时器、实现线程的第三种方式——FutureTask,以及wait和notify方法在生产者消费者模式中的应用。

379

被折叠的 条评论
为什么被折叠?



