java 多线程笔记(概述详解)(含面试题)

目录

一,多线程概述

1 什么是进程?什么是线程?进程与程序的区别?

2  为什么要使用多线程?

3 并行与并发的区别?

4 单核cpu能做到真正的多线程并发吗?

二,多线程的实现

1  第一种实现方式:

2 第二种实现方式:

     采用匿名内部类方式:  

    3 第三种实现方式:实现callable接口

start与run方法的区别:    

三 ,线程的生命周期 

1 生命周期包括哪几个阶段? 

2  完整的生命周期图如下:

四 线程的相关方法(API) 

1  获取线程的名字: mythread.getName();

2  修改线程的名字: mythread.setName("t0");

3 获取当前线程对象:Thread currThread = Thread.currentThread();

4 .start():     1.启动当前线程     2.调用线程中的run方法

5 .sleep(long millitime):线程休眠一段时间,放弃占用的cpu时间片,进入阻塞状态。

6 .interrupt();  终止线程睡眠 , 原理是依靠异常处理机制进行异常抛出。        

 7 .stop(): 强行终止线程(强行将线程杀死),已过时,原因:容易丢失数据

8  如可的安全终止线程?

五 线程调度 

1 常见的线程调度有哪些?

 六 线程安全问题

1 什么是线程安全问题呢?           线程安全问题是指:多个线程对同一个共享数据进行操作时,线程没来得及更新共享数 据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

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

3 如何解决线程安全问题?

 4 线程安全问题模拟

方法一:同步代码块:

方法二:扩大同步代码块

方法三:同步实例方法:在实例方法上加synchronized

在静态代码快上使用synchronized

 5  实际开发中如何解决线程安全问题

6 使用局部变量时选择StringBuffer还是StringBuilder?

  七  死锁的发生和避免

    1 什么是死锁?

   2  发生死锁的条件是什么? 

   3  如何避免死锁?       

4  死锁代码简单实现 

死锁关键代码:

 八 守护线程概述

1  Java语言中线程分为哪两大类?

2 守护线程有什么特点?

代码实现:

九  定时器概述 

1   定时器的作用?

2  定时器的三种实现

  生产者和消费者模式: 

1  任何一个Java对象都有wait()和notify()方法吗?

2  wait()和notify()方法是线程对象方法吗?

 3 wait()方法和notify()方法的作用?

 5  什么是生产者和消费者模式?

6  Java中notify和notifyAll的区别

7  生产者消费者简单代码实现


一,多线程概述

1 什么是进程?什么是线程?进程与程序的区别?



   进程:是一个程序执行一次的过程【即动态的应用程序】具有一个动态的过程即从                            新建到死亡(也叫具有生命周期)

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

   区别:程序:静态的代码集  进程:动态执行的程序

   注意: 一个进程包含多个线程

              一个线程拥有一个【独立的栈空间】

2  为什么要使用多线程?

        更好的利用cpu资源及提高一个进程的执行效率

3 并行与并发的区别?

   并行:多个CPU同时执行多个任务
   并发:一个CPU(采用时间片)同时执行多个任务(即多个线程同时执行【通过争夺cpu时间片】)

4 单核cpu能做到真正的多线程并发吗?

    不能 

二,多线程的实现

1  第一种实现方式:

    将此类声明为Thread的子类(即继承Thread),并重写run方法 

public class Threadtest01 {
	public static void main(String[] args) {
         MyThread mythread = new MyThread();    //新建分支线程对象
         //通过start()方法启动该分支线程
         mythread.start();
         for(int i = 0 ;i<100;i++) {
			System.out.println("主线程--->"+i);
		}    
    }
}
//创建一个Thread的子类并重写run方法
class MyThread  extends Thread{
	@Override
	public void run() {
		for(int i = 0 ;i<100;i++) {
			System.out.println("分支线程1--->"+i);
		}    
	}	
}

2 第二种实现方式:

  将类实现java.long.Runnable接口,并实现run方法(或者直接可以采用匿名内部类方式)

    

public class Threadtest01 {
	public static void main(String[] args) {
        //thread thread = new thread();
		//Thread  t = new Thread(thread);   
        //上两行代码的简化
		Thread  t = new Thread(new thread());  //将可运行类封装成一个线程对象,
         //通过start()方法启动该分支线程
         t.start();
         for(int i = 0 ;i<100;i++) {
			System.out.println("主线程--->"+i);
		}    
    }
}
//创建一个类实现Runnable接口
class thread implements Runnable{
	@Override
	public void run() {
		for(int i = 0;i<100;i++){
		System.out.println("分支线程1--->"+i);
	}	
}

     采用匿名内部类方式:  

public class Threadtest01 {

	public static void main(String[] args) {
		//采用匿名内部类
		Thread  t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {

				for(int i = 0;i<10 ;i++) {
					System.out.println("分支线程1--->"+i);
				}	
			}
		});

         //通过start()方法启动该分支线程
         t1.start();

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

    3 第三种实现方式:实现callable接口

         1  jdk8新特性,需要借助FuturTask类

         2  优点:可以获取线程的返回值,方法可以抛出异常,支持泛型的返回值。

         3  缺点: 该方法会执行效率比较低

         4 代码简单实现:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * @author zhang   
 *    缺点: 该方法会执行效率比较低
 *    优点: 但可以拿到线程的执行结果
 */
public class callable接口实现 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//创建一个“未来任务类”对象,并采用匿名内部类形式创建
		FutureTask task = new FutureTask(new Callable() {
			@Override
			public Object call() throws Exception { //call()方法相当于run()方法,只不过call()方法会有返回值。
				System.out.println("call mythread begin");
				Thread.sleep(1000);
				System.out.println("call mythread end");
				return 100;
			}
		});
		//创建线程对象
		Thread t = new Thread(task);
		t.setName("t");
		//启动线程
		t.start();
		//获取线程的返回值
		Object o = task.get();  //获取返回值可以抛出异常!
		System.out.println(t.getName()+"线程的返回结果:"+o);
		//get()方法是为了拿另一个线程的返回值,所以主线程此时会阻塞,直到拿到线程的返回值。
		System.out.println("Hello World!!");
	}

}

start与run方法的区别:    

 start() 启动线程:

         在JVM开辟一块新的栈空间,该方法就结束了(线程启动成功),启动成功的线程会自动调用run方法压栈在底部,从而实现与其他线程并发执行。
     run():

        直接调用run方法不会启动线程;还是当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行以下代码

三 ,线程的生命周期 

1 生命周期包括哪几个阶段? 

   线程的生命周期包含5个阶段,包括:新建状态、就绪状态、运行状态、阻塞状态、死亡状态。

  • 新建:new出来的线程

  • 就绪(可执行状态):调用start()方法 ,处于等待争夺cpu时间片的状态

  • 运行:run方法的开始执行

  • 阻塞:在运行状态的时候遇到阻塞事件,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

  • 死亡:【run()方法的结束】如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

2  完整的生命周期图如下:

四 线程的相关方法(API) 

1  获取线程的名字: mythread.getName();

2  修改线程的名字: mythread.setName("t0");

3 获取当前线程对象:Thread currThread = Thread.currentThread();

4 .start():     1.启动当前线程     2.调用线程中的run方法

5 .sleep(long millitime):线程休眠一段时间,放弃占用的cpu时间片,进入阻塞状态。

6 .interrupt();  终止线程睡眠 , 原理是依靠异常处理机制进行异常抛出。
        

public class Threadtest01 {

	public static void main(String[] args) {

         MyThread mythread = new MyThread();    //新建分支线程对象
         Thread  t = new Thread(new thread());  //将可运行类封装成一个线程对象,

         //修改线程的名字
         mythread.setName("t0");
         t.setName("t");
         //获取线程的名字
         System.out.println(mythread.getName());

        //返回对当前正在执行的线程对象的引用(即获取当前线程)
		Thread currThread = Thread.currentThread();
        //获取当前对象的名字
		System.out.println(currThread.getName());

         //通过start()方法启动该分支线程
         mythread.start();
         t.start();

         //终止t线程睡眠 , 原理是依靠异常处理机制进行异常抛出。  
         t.interrupt();
       
         
        //当前线程进入休眠5秒的状态(主线程休眠5s)
        try {
			//currThread.sleep(1000*5);
			Thread.sleep(1000*5);
            //注意:sleep()方法是静态方法所以t.sleep会转为Thread.sleep,并不会是t线程进入睡眠
			//t.sleep(1000*5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
         //5s后执行
         for(int i = 0 ;i<100;i++) {
			System.out.println("主线程--->"+i);
		}    
    }
}

//创建一个Thread的子类并重写run方法

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

//创建一个类实现Runnable接口

class thread implements Runnable{

	@Override
	public void run() {
       
       //该线程将睡眠一年 
        try {
			Thread.sleep(1000 * 60*60*24*365 );
		} catch (InterruptedException e) {
            //打印异常信息
			e.printStackTrace();
		}
        //下面for语句将在一年后执行
		for(int i = 0;i<100;i++){
		System.out.println("分支线程2--->"+i);
	}
	
}

 7 .stop(): 强行终止线程(强行将线程杀死),已过时,原因:容易丢失数据

8  如可的安全终止线程?

public class Threadtest01 {

	public static void main(String[] args) {

         MyThread mythread = new MyThread();    
         mythread.start();
         
         try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			  }
         //5s后强行终止该进程
         /*mythread.stop();*/
         //合理终止一个线程
		 mythread.run=false;

         for(int i = 0 ;i<100;i++) {
			System.out.println("主线程--->"+i);
		}    
    }
}
class MyThread  extends Thread{
    boolean run = true;
	@Override
	public void run() {
      if(run){
		for(int i = 0 ;i<100;i++) {
            //利用.sleep()方法使其每过一秒就打印下面的信息
            try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			  }
			System.out.println("分支线程1--->"+i);
		}
      }else{
            return;
           }    
	}	
}

五 线程调度 

1 常见的线程调度有哪些?

    抢占式调度模型:根据线程的优先级,优先级高的抢占的cpu时间片就可能会多一些(java就是该模型)

    均分式调度模型:平均分配cpu时间片

 实例方法:
        1   setPriority(): 设置优先级 优先级越高,抢占的cpu时间片就相对多一点 。               
        2   join() : 合并线程 ,但当前线程会进入阻塞状态,直到合并的线程执行结束,当前线程才可以继续执行。

静态方法:
        1   yied() :让位方法,暂停当前正在执行的对象,让位执行其他线程,(即让出cpu时间片从【运行状态】回到【就绪状态】重新进行cpu时间片的争夺)

Thread t =Thread.currentThread();
//获取线程优先级
t.getPriority()
//修改线程优先级
t.setPriority(10);

//输出线程的最大,最小,默认优先值
System.out.println(Thread.MAX_PRIORITY);   //10
System.out.println(Thread.MIN_PRIORITY);   //1
System.out.println(Thread.NORM_PRIORITY);  //5

//注意:每个线程的初始默认值都是5
public class threadtest02 {

	public static void main(String[] args) {

	    Thread t =Thread.currentThread();
        //设置优先级
	    t.setPriority(10);
	    
	    
	    Thread th = new Thread(new mythread01());
	    th.setName("tttt");
	    th.start();
	    
	    System.out.println(t.getName()+"---->begin");
	    try {
            //将tttt线程合并到主线程,主线程此时会进入入阻塞状态,
            //直到合并的线程执行结束,当前线程才可以继续执行。
			th.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	    for(int i = 0;i<100;i++) {
	    	System.out.println(t.getName()+"--->"+i);
	    }
	    System.out.println(t.getName()+"---->end");
	}

}
class mythread01 implements Runnable{
    
	@Override
	public void run() {
		Thread.currentThread().setPriority(10);
		for(int i = 0;i<100;i++) {
			if(i%10 ==0 ) {
                //只要i是10的倍数就会进行一次让位操作
				Thread.yield();
			}
	    	System.out.println(Thread.currentThread().getName()+"--->"+i);
	    }
	}
	
}

 六 线程安全问题

1 什么是线程安全问题呢?
           线程安全问题是指:多个线程对同一个共享数据进行操作时,线程没来得及更新共享数 据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

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

          发生条件:多线程并发,有共享数据/对象,共享数据发生修改

3 如何解决线程安全问题?

        使用线程【同步机制】:使线程排队执行(不能并发) 

       Java中使用synchronized关键字进行共享对象【锁】机制

异步编程模型:其实就是多线程并发(效率高)

同步编程模型: 排队执行,不能并发

 4 线程安全问题模拟

import java.math.BigDecimal;
public class banktest {

	public static void main(String[] args) {
		account a = new account("V-34333",new BigDecimal("10000.00"));
		System.out.println(a.toString());
		Thread t01 = new Thread(new accountthread(a));
		Thread t02 = new Thread(new accountthread(a));
		
		t01.setName("t01");
		t02.setName("t02");
		
		t01.start();
		t02.start();	
	}  

}
//编写账户对象
class account{
	private String ID;
	private BigDecimal  balance;
	Object o = new Object();
	
	public String getID() {
		return ID;
	}
	public void setID(String iD) {
		ID = iD;
	}
	public BigDecimal getBalance() {
		return balance;
	}
	public void setBalance(BigDecimal balance) {
		this.balance = balance;
	}
	
	
	public account(String iD, BigDecimal balance) {
		super();
		ID = iD;
		this.balance = balance;
	}
	public account() {
		super();
	}
	@Override
	public String toString() {
		return "账户: 账号:" + ID + "   余额:" + balance ;
	}
	
   //取款操作方法:
	public void withdraw(BigDecimal money) {
           
			balance=balance.subtract(money);
	}

}

//编写线程对象
class accountthread implements Runnable{
	    
	private account a;
	public accountthread (account a) {
		this.a=a;
	}
	@Override
	public void run() {
		BigDecimal money = new BigDecimal("5000.00");
			a.withdraw(money);
		System.out.println(Thread.currentThread().getName()+"对"+a.getID()+"账户取款"+money+" 余额:"+a.getBalance());
	}
	
}

上面模拟了两个用户(两个线程)同时对一个账号进行取款操作,通过执行上面的代码就会发现一个很大的安全问题,两个用户明明已经将同一账户的10000钱每人5000的全部取出,结果在大概率的情况下会出现账户上居然还有5000,这个结果是不是让人很是兴奋!当然现实生活中应该是不会出现的,因为后端程序员们会对上面的代码进行安全处理,所以别想那不可能的事啦!现在我们就对上面的代码进行安全的处理。

方法一:同步代码块:

(对每个线程对账户对象的取款方法进行代码块同步也就是给该方法的关键代码快上【锁】,当一个线程优先抢到cpu时间片执行到带有Synchronized关键字的代码块时就会对该代码快进行上锁,上锁后这时若有相同共享对象的线程也要执行该段代码,那不好意思,只有等上一个线程执行结束才可以执行(也就是需要进行排队操作,不能并发))

使用同步监视器(锁)这里锁的的意思是:阻止具有相同共享对象的线程进入代码快执行并发
Synchronized(同步监视器){undefined
//需要被同步的代码
}
//对取款方法进行优化,这样就解决了线程安全问题。
public void withdraw(BigDecimal money) { 
        
		//同步代码块synchronized
		synchronized (this) {    
			//模拟网络延迟,这样就一定就会出现安全问题
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
			balance=balance.subtract(money);
		}
	}

上述的synchronized (this)可以改为synchronized ("abc"),synchronized (o),synchronized (o1)吗?

public void withdraw(BigDecimal money) {
		
		Object o1 = new Object();
		 
		//同步代码块synchronized
		  synchronized (this) {  //可行,当前对象进行上锁
		//synchronized ("abc") { //可行,因为字符串abc对象在方法区的字符串常量池中,是共享对象
		//synchronized (o) {     //可行,因为该对象也是线程的共有对象
		//synchronized (o1) {    //不可行,该对象是每个线程的局部对象,实际会产生两把对象锁
			//模拟网络延迟
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
			balance=balance.subtract(money);
			
		}
	}
}

注意:虽然上面的synchronized ("abc"),synchronized (o)也可行,但这明显太不讲理了,因为无论该线程是否和当前对象是否拥有共享对象/数据都会被锁在外面,也就是说你一个人去取款然后全世界的人在你取款的那一刻都不能取款都得排队等你结束了才能取款,而我们要的效果是对同一个账户同时多个线程取款的其他线程需要排队,连账号都不同当然就不需要排队咯,所以选择一个好的写法很重要。

1 通过上述问题的了解,那有哪些变量有线程安全呢?

  成员变量。 

 java中三大变量
           实例变量      在堆中
           静态变量      在方法区
           局部变量      在栈区      永远不会发生线程安全问题

方法二:扩大同步代码块

在线程对象类中调用账户取款方法进行同步,这样扩大了同步范围,执行效率变低了。

class accountthread implements Runnable{	
	private account a;
	public accountthread (account a) {
		this.a=a;
	}
	@Override
	public void run() {
		BigDecimal money = new BigDecimal("5000.00");
		//扩大同步范围
		//synchronized (this) {  //不可行,因为当前对象是每个线程自己
		synchronized (a) {       //可行,a 是每个线程都在共享一个账户的共享对象
			a.withdraw(money);
		}
		System.out.println(Thread.currentThread().getName()+"对"+a.getID()+"账户取款"+money+" 余额:"+a.getBalance());
	}
	
}

方法三:同步实例方法:在实例方法上加synchronized

               该方法与方法二一样扩大了同步范围,导致执行效率低。

               不灵活,因为该方法锁的一定是this。

               但代码量少了。

public synchronized void withdraw(BigDecimal money) { 
        
			//模拟网络延迟,这样就一定就会出现安全问题
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
			balance=balance.subtract(money);
	}

在静态代码快上使用synchronized

              表示类锁(排它锁),永远只有1把,保证静态变量的安全。

面试题:

public class Threadtest03 {

	public static void main(String[] args) throws InterruptedException {
		Myclass mc = new Myclass();
		thread00 t1 = new thread00(mc);
		thread00 t2 = new thread00(mc);
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		Thread.sleep(1000);//确保t1先执行
		t2.start();	
	}
}

class Myclass{
	public synchronized void dosome() {
		System.out.println("dosome--->begin");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("dosome--->over");
	}
	public void doOther() {
		System.out.println("doOther--->begin");
		System.out.println("doOther--->over");
	}
}
class thread00 extends Thread{
	private Myclass mc;

	public thread00(Myclass mc) {
		super();
		this.mc = mc;
	}
	@Override
	public void run() {
		if(Thread.currentThread().getName().equals("t1")) {
			mc.dosome();
		}
		if(Thread.currentThread().getName().equals("t2")) {
			mc.doOther();
		}	
	}
}

问题1:doOther是否需要等待dosome?

            不需要!

问题2:将doOther加上synchronized是否需要呢?          需要。

public synchronized void doOther() {
		System.out.println("doOther--->begin");
		System.out.println("doOther--->over");
	}

 问题3: 在问题2的基础上做如下改动是否需要等待呢?   不需要。     

Myclass mc1 = new Myclass();
Myclass mc2 = new Myclass();
thread00 t1 = new thread00(mc1);
thread00 t2 = new thread00(mc2);

 问题4:在问题3的基础上在做如下修改是否还需要呢?   需要。

              因为静态方法是类锁,不管创建了几个线程对象,类锁只有一把。       

public synchronized static void dosome() { 
       System.out.println("dosome--->begin");
		try {
			Thread.sleep(1000);
		} 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");
	}

    发现个奇怪的问题!!!!!!

    在上述  题2的基础上将两方法(dosome,doOther)其中的一个方法体加static ,竟然就不需要等待了,题2本来就需要等待,而且加了static不是Myclass就成类锁了吗?为啥就并发了呢?两个同时加就不能并发,着实还没搞明白!!!!!!            

       

   

 5  实际开发中如何解决线程安全问题

     1  尽量使用局部变量代替“实例变量”和“静态变量”

     2  如果必须实例变量,那就创建多个对象,避免实例变量的内存共享。

     3  如果不能使用局部变量,且不能创建多个对象,就使用synchronized,线程同步机制。

6 使用局部变量时选择StringBuffer还是StringBuilder?

   建议使用StringBuilder。

   因为局部变量不存在线程安全问题,而StringBuffer是线程安全的,带有synchronized关键字,这样就导致执行效率没有StringBuilder的高。

  ArrayList 是非线程安全的,Vector  是线程安全的

  HashMap ,HashSet 是非线程安全的,Hashtable是线程安全的。

   

  七  死锁的发生和避免

    1 什么是死锁?

        死锁 :是指两个或两个以上的进程在执行过程中,去争夺同样的一个共享资源而造成的一种互相等待的现象,若无人工干预,等待线程将永远等待,无法继续执行。

   2  发生死锁的条件是什么? 

        1  互斥条件:                    一个资源每次只能被一个进程使用。
        2   请求与保持条件:         一个进程因请求资源而阻塞时,对已获得的资源保持不放。
        3   不剥夺条件:                  进程已获得的资源,在末使用完之前,不能强行剥夺。
        4   循环等待条件:              若干进程之间形成一种头尾相接的循环等待资源关系。

   3  如何避免死锁?       

        若已经造成了死锁:只有破环四个必要条件的其中一个就行

        若还未造成死锁:

                      1   在系统设计、进程调度等方面注意如何不让这四个必要条件成立。

                      2  对资源的分配要给予合理的规划;如确定资源的合理分配算法,避免进程永久占据系统资源,防止进程在处于等待状态的情况下占用资源。        

4  死锁代码简单实现 

public class DeadLock {

	public static void main(String[] args) {
		Object o1 = new Object();
		Object o2 = new Object();
		
		threadTest1 t1 = new threadTest1(o1,o2);
		threadTest1 t2 = new threadTest1(o1,o2);
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		t2.start();
	}

}

class threadTest1 extends Thread{
	Object o1;
	Object o2;
	public threadTest1(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}
	public threadTest1() {
	}
	@Override
	public void run() {
		synchronized (o1) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o2) {
				//System.out.println(Thread.currentThread().getName()+"能被输出吗?");
			}
		}
	}
	
}
class threadTest2 extends Thread{
	Object o1;
	Object o2;
	public threadTest2(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}
	public threadTest2() {
	}
	@Override
	public void run() {
		synchronized (o2) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o1) {
				//System.out.println(Thread.currentThread().getName()+"能被输出吗?");
			}
		}
	}
	
}

死锁关键代码:

//线程1
Synchorized(objectA){
    Synchorized(objectB){
        //操作
    }
}
   //线程2 
   Synchorized(objectB){ 
       Synchorized(objectA){ 
           //操作
       } 
 } 

 八 守护线程概述

1  Java语言中线程分为哪两大类?

    【用户线程】和【守护线程】(后台线程)垃圾回收线程就是一个守护线程!

2 守护线程有什么特点?

     一般守护线程都是一个死循环,只要所有的用户线程都结束,守护进程会自动结束

//将线程设置为守护线程
bdt.setDaemon(true);

代码实现:

public class 守护线程 {

	public static void main(String[] args) {
		backdatethread bdt = new backdatethread();
		bdt.setName("自动备份线程");
		//将自动备份线程设置为守护线程
		bdt.setDaemon(true);
		bdt.start();
		for(int i = 0;i<10;i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"---------->"+i);
		}
	}

}
class backdatethread extends Thread{
    
	int i = 0;
	@Override
	public void run() {
		while(true) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"--->"+(++i));
		}
	}
	
}

九  定时器概述 

1   定时器的作用?

     间隔特定的时间运行特定的程序

2  定时器的三种实现

    1 使用sleep()方法:

        这是最简单的,但实际用的很少,创建一个thread并采用匿名内部类实现。

public class Task1 {  
    public static void main(String[] args) {  
        //采用匿名内部类
       Thread thread = new Thread(new Runnable() {  
            public void run() {  
                while (true) { 
                    System.out.println("Hello World!");    
                    //定时每隔一秒运行一次
                    try {  
                        Thread.sleep(1000);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }  
            }  
        });    
        thread.start();  
    }  
}

    2 使用Java类库下的Java.util.Timer:

       1   Timer实例可以调度多任务,它是线程安全的。 当Timer的构造器被调用时,它创建了一个线程,这个线程可以用来调度任务

       2  相对于1来说;可以控制任务的启动和取消,但实际用的也较少。

//主要代码
Timer t = new Timer();
t.schedule(new TimerTask() {
			@Override
			public void run() {
			
			}
		}, fistdate, 1000);

//构造方法:
Timer()	   创建一个新计时器。
Timer​(boolean isDaemon)	 创建一个新的计时器,其关联的线程可以指定为 run as a daemon 。
Timer​(String name)	创建一个新的计时器,其关联的线程具有指定的名称。
Timer​(String name, boolean isDaemon)	创建一个新的计时器,其关联的线程具有指定的名称,并且可以指定为 run as a daemon 。

//方法摘要:
void	cancel()	终止此计时器,丢弃当前计划的任何任务。
int	purge()	  从此计时器的任务队列中删除所有已取消的任务。
void	schedule​(TimerTask task, long delay)	在指定的延迟后安排指定的任务执行。
void	schedule​(TimerTask task, long delay, long period)	在指定 的延迟之后开始,为重复的 固定延迟执行安排指定的任务。
void	schedule​(TimerTask task, Date time)	计划在指定时间执行指定的任务。
void	schedule​(TimerTask task, Date firstTime, long period)	从指定时间开始,为重复的 固定延迟执行安排指定的任务。
void	scheduleAtFixedRate​(TimerTask task, long delay, long period)	在指定的延迟之后开始,为重复的 固定速率执行安排指定的任务。
void	scheduleAtFixedRate​(TimerTask task, Date firstTime, long period)	从指定时间开始,为重复的 固定速率执行安排指定的任务。
public class Task2 {
	public static void main(String[] args) throws ParseException {
		Timer t = new Timer();
		SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date fistdate = ft.parse("2022-03-27 11:07:50");	//该时间是定时执行时间,可自己设定	
		t.schedule(new logTimeLask(), fistdate, 1000);
	}
}
//TimeTask是抽象对象不能new但可以通过匿名内部类实现
class logTimeLask extends TimerTask{
	@Override
	public void run() {
		Date newdate = new Date();
		SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS");
		String date = ft.format(newdate);
		System.out.println(date+"完成了一次数据备份");
	}
	
}

    3 使用Spring框架中提供的SpringTask框架 

10 生产者和消费者模式: 

面试题:

1  任何一个Java对象都有wait()和notify()方法吗?

    是的,因为这两个方式是object类中自带的

2  wait()和notify()方法是线程对象方法吗?

   不是,

 3 wait()方法和notify()方法的作用?

  wait():让当前调用o.wait()方法的线程进入无限等待状态,并且释放之前占有的o对象的锁

  notify():唤醒正在o对象上等待的线程,只是通知,不会释放之前占有的o对象的锁

4  wait()和notify()方法建立在线程同步的基础之上,因为多线程同时操作一个仓库,有线程安全问题。 

 5  什么是生产者和消费者模式?

     这是一种特殊的业务模式, 生产线程负责生产,消费线程负责消费,两者需要达到均衡,需要使用wait()方法和notify()方法。

6  Java中notify和notifyAll的区别

       调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器

       调用notifyAll方法,那么等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁定,这就是为什么在循环上调用wait,因为如果多个线程被唤醒,那么线程是将获得锁定将首先执行,它可能会重置等待条件,这将迫使后续线程等待 

7  生产者消费者简单代码实现

      模拟一个仓库只能生产一个消费一个才能继续在生产下一个

public class WorNotify {
	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("t1");
		t2.setName("t2");
		t1.start();
		t2.start();
	}
}
class producer implements Runnable {
	private List list;
	public producer(List list) {
		this.list = list;
	}
	@Override
	public void run() {
		synchronized (list) {
			while (true) {
				if (list.size() > 0) {
					try {
						list.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else {
					list.add(new Object());
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "--->" + list.get(0));
					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 {
						list.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				Object o = list.remove(0);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "--->" + o);
				list.notifyAll();
			}
		}
	}
}

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A.丫三岁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值