java多线程

多线程

1、什么是进程?什么是线程?

进程是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程。

  java中的多线程机制,目的就是为了提高程序的处理效率。

例:

package javaCoreTest;

/*
 * 判断以下程序出垃圾回收线程之外有几个线程?
 * 	1个线程(因为程序只有1个栈)
 * 
 *	main begin
	m1 begin
	m2 begin
	m3 execute
	m2 over
	m1 over
	main over
	一个栈中,自上而下的顺序依次逐行执行!
 */

public class ThreadTest01 {

	public static void main(String [] args) {
		System.out.println("main begin");
		m1();
		System.out.println("main over");
	}

	private static void m1() {
		// TODO Auto-generated method stub
		System.out.println("m1 begin");
		m2();
		System.out.println("m1 over");
	}

	private static void m2() {
		// TODO Auto-generated method stub
		System.out.println("m2 begin");
		m3();
		System.out.println("m2 over");
	}

	private static void m3() {
		// TODO Auto-generated method stub
		System.out.println("m3 execute");
	}
}

2、对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后,会先启动JVM,而JVM就是一个进程。
JVM在启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责 看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,
一个是垃圾回收线程,一个是执行main方法的主线程。

3、两个进程之间是相互独立的,不共享资源。
线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

4、使用了多线程机制之后,main方法结束是不是有可能程序也不会结束?
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

5、对于单核的CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没有问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做。

6、java 语言中实现线程有两种方式,有哪两种呢?
java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了。

  • 第一种:

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

	//定义线程类
		public class MyThread extends Thread{
		public void run(){

		}
	}
//创建线程对象
MyThread myThread = new MyThread();
//启动线程
my.Thread.start();

例:

package javaCoreTest;

/*
 * 实现线程的第一种方式:
 * 	编写一个类,直接继承java.lang.Thread,重写run()方法
 * 
 * 怎么创建线程对象?
 * 	new就行了
 *怎么启动线程?
 *	调用线程对象的start()方法
 */

public class ThreadTest02 {

	public static void main(String [] args) {
		//这里是main方法,属于主线程,在主栈中运行
		//新建一个分支线程对象
		MyThread myThread = new MyThread();
		
		//启动线程
		
		//如果直接调用run方法,不会启动线程,不会分配新的分支栈(这种方式是单线程)
		//myThread.run();
		
		//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务【开辟一个新的栈空间】完成之后,瞬间就结束了
		//线程就启动成功,会自动调用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() {
		// 编写程序,这段程序运行在分支线程中()
		super.run();
		for(int i = 0; i < 1000; i++) {
			System.out.println("分支线程=====" + i);
		}
	}
	
}
  • 第二种:

    编写一个类,实现java.lang.Runnable接口

		//定义一个可运行的类
		public class MyRunnable implements Runnable{
			public void run(){
			
			}
		}
		//创建一个线程对象
		Thread t = new Thread(new MyRunnable());
		//启动线程
		t.start();

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

package javaCoreTest;

/*
 * 实现线程的第二种方式:编写一个类,实现java.lang.Runnable接口
 */
public class ThreadTest03 {

	public static void main(String [] args) {
		//创建一个可运行的对象
		//MyRunnable t = new MyRunnable();
		
		//将可运行的对象封装成一个线程对象
		//Thread tt = new Thread(t);
		
		//合并以上代码
		Thread tt = new Thread(new MyRunnable());
		
		//启动线程
		tt.start();
		
		for(int i = 0; i < 1000; i++) {
			System.out.println("主线程=====" + i);
		}
	}
}

class MyRunnable implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0; i < 1000; i++) {
			System.out.println("分支线程=====" + i);
		}
	}
	
}
package javaCoreTest;

/*
 * 采用匿名内部类
 */

public class ThreadTest04 {

	public static void main(String [] args) {
		//创建线程对象,采用匿名内部类的方式
		Thread t = new Thread (new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				for(int i = 0; i < 1000; i++) {
					System.out.println("分支线程====" + i);
				}
			}
		});
		
		t.start();
		
		for(int i = 0; i < 1000; i++) {
			System.out.println("主线程====" + i);
		}
	}
}

7、线程的生命周期

  • 新建状态

  • 就绪状态

  • 运行状态

  • 阻塞状态

  • 死亡状态

package javaCoreTest;

/*
 * 1.怎么获取当前线程对象?
 * 		Thread t = Thread.currentThread();
 * 		返回值t就是当前线程。
 * 
 * 2.获取线程对象的名字?
 * 		String name = 线程对象.getName();
 * 
 * 3.修改线程对象的名字?
 * 		线程对象.setName("线程名字");
 * 
 * 4.当线程没有设置名字的时候,默认的名字有什么规律?(了解)
 * 		Thread-0
		Thread-1
		Thread-2
		Thread-3
		Thread-4
		.........
 */

public class ThreadTest05 {

	public static void main(String [] args) {
		
		//currentThread就是当前线程对象
		//这个代码出现在main方法当中,所以当前线程就是主线程。
		Thread currentThread = Thread.currentThread();
		System.out.println(currentThread.getName());//main
		
		//创建线程对象
		MyThread05 t = new MyThread05();
		
		//设置线程的名字--setName方法
		//t.setName("tttt");
		
		//获取线程的名字--getName方法
		String tName = t.getName();
		System.out.println(tName);//Thread-0

		MyThread05 t2 = new MyThread05();
		System.out.println(t2.getName());//Thread-1

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

class MyThread05 extends Thread{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		
		for(int i = 0; i < 1000; i++) {
			//currentThread就是当前线程对象,那么当前线程是谁呢?
			//当t线程执行run方法,那么这个当前线程就是t
			//当t2线程执行run方法,那么这个当前线程就是t2
			Thread currentThread = Thread.currentThread();
			System.out.println(currentThread.getName());
			System.out.println("分支线程====" + i);
		}
	}
	
}
package javaCoreTest;

/*
 * 关于线程的sleep方法:
 * 	static void sleep (long millis)
 * 	1、静态方法:Thread.sleep(1000);
 * 	2、参数是毫秒
 * 	3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占用CPU时间片,让给其他线程使用。
 */

public class ThreadTest06 {

	public static void main(String [] args) {
		
		//让当前线程进入休眠,睡眠5秒
		/*try {
			Thread.sleep(1000 * 5);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
		
		//5秒之后执行这里的代码
		//System.out.println("hello world!");
		
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "====>" + i);
			
			//睡眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
		
	}
}
package javaCoreTest;

/*
 * 关于Thread.sleep()方法的一个面试题:
 */

public class ThreadTest07 {

	public static void main(String [] args) {
		//创建线程对象
		Thread t = new MyThread07();
		t.setName("t");
		t.start();
		
		//调用sleep方法
		try {
			//问题:这行代码会让线程t进入休眠状态吗?
			//不会。
			t.sleep(1000 * 5);//在执行的时候还是会转换成:Thread.sleep(1000 * 5);
							  //这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠状态。
							  //hello world5秒后输出
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//5秒后执行
		System.out.println("hello world!");
	}
}

class MyThread07 extends Thread{
	public void run() {
		for(int i = 0; i < 10000; i++) {
			System.out.println(Thread.currentThread().getName() + "=====>" + i);
		}
	}
}
package javaCoreTest;

/*
 * sleep睡眠太久了,如果希望半道上醒来,应该怎么办?怎么唤醒一个正在睡眠的线程?
 * 	注意:这个不是中断线程的执行,是中止线程的睡眠。
 */

public class ThreadTest08 {

	public static void main(String [] args) {
		Thread t = new Thread(new MyRunnable08());
		
		t.setName("t");
		t.start();
		
		//希望5秒之后,t线程醒来
		try {
			Thread.sleep(1000 * 5);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//中断t线程的睡眠(这种中断睡眠的方式依靠了java的异常处理机制)
		t.interrupt();//干扰
	}
}

class MyRunnable08 implements Runnable{
	
	//重点:run()当中的异常不能throws,只能try..catch
	//因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName() + "=====>+ begin" );
		//睡眠1年
		try {
			Thread.sleep(1000 * 60 * 60 * 24 * 365);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			//打印异常信息【如果不想显示可以注释掉】
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "=====>+ end");
	}
	
}
package javaCoreTest;

/*
 * 在java中怎么强行终止一个线程的执行?
 * 	这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了。
 * 	线程没有保存的数据将会丢失,不建议使用。
 */

public class ThreadTest09 {

	public static void main(String [] args) {
		Thread t = new Thread(new MyRunnable09());
		t.setName("t");
		t.start();
		
		//模拟5秒
		try {
			Thread.sleep(5 * 1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//5秒之后强行终止t线程
		t.stop();//已过时,不建议使用
	}
}

class MyRunnable09 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "=====>" + i);
			
			try {
				//睡1秒
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}
package javaCoreTest;

/*
 * 怎么合理的终止一个线程的执行?
 * 以下这种方式很常用。
 */

public class ThreadTest10 {

	public static void main(String [] args) {
		MyRunnable10 r = new MyRunnable10();
		Thread t = new Thread(r);
		
		t.setName("t");
		t.start();
		
		//模拟5秒
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//终止线程
		//想要什么时候终止t的执行,就把标记改为false,结束。
		r.run = false;
	}
}

class MyRunnable10 implements Runnable{
	
	boolean run = true;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		for(int i = 0; i < 10; i++) {
			
			if(run) {
				System.out.println(Thread.currentThread().getName() + "====>" + i);
				
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else {
				//在结束之前没有保存的数据可以在这里保存
				
				//终止当前线程
				return;
			}
		}
	}
	
}

8、线程的调度(了解)
(1)、常见的线程调度模型

  • *抢占式调度模型

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

  • *均分式调度模型

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

    (2)、java中提供了哪些方法是和线程调度有关系的呢?

    • 实例方法:

       	void setPriority()设置线程优先级
       		int getPriority()获取线程优先级
       		最低优先级1
       		默认优先级5
       		最高优先级10
       		优先级高的获取CPU 时间片可能会多一些。
       		(但也不完全是,大概率是多的)
      

例:

package javaCoreTest;

/*
 * 了解:关于线程的优先级
 */

public class ThreadTest11 {

	public static void main(String [] args) {
		//设置主线程的优先级为1
		Thread.currentThread().setPriority(1);
		
//		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 MyRunnable11());
		//设置分支线程优先级为10
		t.setPriority(10);
		t.setName("t");
		t.start();
		
		//优先级较高,只是抢到的CPU时间片相对多一些
		for(int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName() + "===>" + i);
		}
	}
}

class MyRunnable11 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		//获取线程优先级
//		System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());
		for(int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName() + "===>" + i);
		}
	}
	
}
 - 静态方法:

			static void yield()让位方法
			暂停当前正在执行的线程对象,并执行其他线程
			yield()方法不是阻塞方法,让当前线程从“运行状态”
			回到“就绪状态”,让给其他线程使用。
			注意:在回到就绪状态之后,有可能还会再次抢到。

例:

package javaCoreTest;

/*
 * 让位,当前线程暂停,回到就绪状态,让给其他线程
 * 静态方法:Thread.yield();
 */

public class ThreadTest12 {

	public static void main(String [] args) {
		Thread thread = new Thread(new MyRunnable12());
		thread .setName("t");
		thread.start();
		
		for(int i = 1; i <= 1000; i++) {
			System.out.println(Thread.currentThread().getName() + "--->" + i);
		}
	}
}

class MyRunnable12 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 1; i <= 1000; i++) {
			//每100个让位1次
			if(i % 100 == 0) {
				Thread.yield();
			}
			System.out.println(Thread.currentThread().getName() + "--->" + i);
		}
	}
	
}
 - 实例方法:

		void join() 合并线程
		例:
			class MyThread1 extends Thread{
				public void doSome(){
					MyThread2 t = new MyThread2();
					t.join();//当前线程进入阻塞,t线程执行,直到t线程结束,
							   //当前线程才可以继续。
				}
			}

			class MyThread2 extends Thread{

			}

例:

package javaCoreTest;

/*
 * 合并线程
 */

public class ThreadTest13 {

	public static void main(String [] args) {
		System.out.println("main begin");
		
		Thread t = new Thread(new MyRunnable13());
		t.setName("t");
		t.start();
		
		//合并线程
		try {
			t.join();//t合并到当前线程中,当前线程受阻塞,t线程执行,直到结束
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("main over");
	}
}

class MyRunnable13 implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 1; i <= 1000; i++) {
			System.out.println(Thread.currentThread().getName() + "--->" + i);
		}
	}
}

9、关于多线程并发环境下,数据的安全问题
(1)为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义、线程的对象创建、线程的启动等,都已经实现了,这些代码我们都不需要编写。

最重要的是:编写的程序需要放到一个多线程的环境下运行,更需要关注的是这些数据在多线程并发的环境下是否是安全的。

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

三个条件:
*多线程并发
*有共享数据
*共享数据有修改行为
满足以上三个条件,存在线程安全问题

(3)怎么解决线程安全问题?
线程排队执行(不能并发)
用排队执行解决线程安全问题,这种机制被称为线程同步机制。
线程同步就是线程排队,线程排队就会牺牲一部分效率。

  • 补充两个专业术语:

    异步编程模型:
    线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做异步编程模型。
    其实就是:多线程并发(效率较高)

    异步就是并发。

    同步编程模型:
    线程1和线程2,在线程1执行的时候,必须等待线程2执行结束,或者说在线程2执行的时候,必须等待线程1执行结束,两个线程之间发生了等待关系,这就是同步编程模型。(效率较低)
    其实就是:线程排队执行

    同步就是排队。

  • 补充Java中的三大变量?

     		实例变量:在堆中
     		静态变量:在方法区
     		局部变量:在栈中
     	
     
    
     		以上三大变量中:局部变量永远都不会存在线程安全问题,
     		因为局部变量不共享(一个线程一个栈)局部变量在栈中,
     		所以局部变量永远都不会共享。
    
     		实例变量在堆中,堆只有一个
     		静态变量在方法区中,方法区只有一个
     		堆和方法区都是多线程共享的,所有可能存在线程安全问题。
    

10、以后的开发中,应该怎么解决线程安全问题?
是一上来就选择线程同步吗?synchronized
不是,线程同步会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制。

  • 第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

  • 第二种方案:

    如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)

  • 第三种方案:

    如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

面试题1:
package javaCoreTest;

//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为doOther方法没有synchronized

public class ThreadExam01 {

	public static void main(String [] args) {
		MyClass mClass = new MyClass();
		
		Thread t1 = new MyThread1(mClass);
		Thread t2 = new MyThread1(mClass);
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		try {
			Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t2.start();
	}
}

class MyThread1 extends Thread{
	private MyClass mClass;
	
	public MyThread1(MyClass mClass) {
		super();
		this.mClass = mClass;
	}

	public void run() {
		if(Thread.currentThread().getName().equals("t1")) {
			mClass.doSome();
		}
		if(Thread.currentThread().getName().equals("t2")) {
			mClass.doOther();
		}
	}
}

class MyClass{
	public synchronized void doSome() {
		System.out.println("doSome begin");
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("doSome end");
	}
	
	public void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther end");
		
	}
}

面试题2:

package javaCoreTest;

//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//需要,因为MClass只有一个

public class ThreadExam02 {

	public static void main(String [] args) {
		MyClass2 mClass = new MyClass2();
		
		Thread t1 = new MyThread2(mClass);
		Thread t2 = new MyThread2(mClass);
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		try {
			Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t2.start();
	}
}

class MyThread2 extends Thread{
	private MyClass2 mClass;
	
	public MyThread2(MyClass2 mClass2) {
		super();
		this.mClass = mClass2;
	}

	public void run() {
		if(Thread.currentThread().getName().equals("t1")) {
			mClass.doSome();
		}
		if(Thread.currentThread().getName().equals("t2")) {
			mClass.doOther();
		}
	}
}

class MyClass2{
	public synchronized void doSome() {
		System.out.println("doSome begin");
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("doSome end");
	}
	
	public synchronized void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther end");
		
	}
}

面试题3:

package javaCoreTest;

//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为MClass对象是两个,两把锁

public class ThreadExam03 {

	public static void main(String [] args) {
		MyClass3 mClass1 = new MyClass3();
		MyClass3 mClass2 = new MyClass3();
		
		Thread t1 = new MyThread3(mClass1);
		Thread t2 = new MyThread3(mClass2);
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		try {
			Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t2.start();
	}
}

class MyThread3 extends Thread{
	private MyClass3 mClass;
	
	public MyThread3(MyClass3 mClass1) {
		super();
		this.mClass = mClass1;
	}

	public void run() {
		if(Thread.currentThread().getName().equals("t1")) {
			mClass.doSome();
		}
		if(Thread.currentThread().getName().equals("t2")) {
			mClass.doOther();
		}
	}
}

class MyClass3{
	public synchronized void doSome() {
		System.out.println("doSome begin");
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("doSome end");
	}
	
	public synchronized void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther end");
		
	}
}

面试题4:

package javaCoreTest;

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

public class ThreadExam04 {

	public static void main(String [] args) {
		MyClass4 mClass1 = new MyClass4();
		MyClass4 mClass2 = new MyClass4();
		
		Thread t1 = new MyThread4(mClass1);
		Thread t2 = new MyThread4(mClass2);
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		try {
			Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t2.start();
	}
}

class MyThread4 extends Thread{
	private MyClass4 mClass;
	
	public MyThread4(MyClass4 mClass1) {
		super();
		this.mClass = mClass1;
	}

	public void run() {
		if(Thread.currentThread().getName().equals("t1")) {
			mClass.doSome();
		}
		if(Thread.currentThread().getName().equals("t2")) {
			mClass.doOther();
		}
	}
}

class MyClass4{
	//synchronized出现在静态方法中,找类锁
	public synchronized static void doSome() {
		System.out.println("doSome begin");
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("doSome end");
	}
	
	public synchronized static void doOther() {
		System.out.println("doOther begin");
		System.out.println("doOther end");
		
	}
}

关于死锁:

package javaCoreTest;

/*
 * 死锁代码要会写。
 * 一般面试官要求程序员会写,因为只有会写,才会在以后的开发中注意这个事
 * 因为死锁很难调试。
 * 
 * synchronized在开发中最好不要嵌套使用,一不小心可能会导致死锁现象的发生
 */

public class DeadLock01 {

	public static void main(String [] args) {
		Object o1 = new Object();
		Object o2 = new Object();
		
		//t1和t2两个线程共享o1,o2
		Thread t1 = new MyThread001(o1, o2);
		Thread t2 = new MyThread002(o1, o2);
		
		t1.start();
		t2.start();
	}
}

class MyThread001 extends Thread{
	Object o1;
	Object o2;
	
	public MyThread001(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}
	
	public void run() {
		synchronized (o1) {
			synchronized (o2) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

class MyThread002 extends Thread {
	Object o1;
	Object o2;
	
	public MyThread002(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}
	
	public void run() {
		synchronized (o1) {
			synchronized (o2) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

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

(2)守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意:主线程main方法是一个用户线程。

(3)守护线程用在什么地方呢?
每天零点的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到零点的时候就备份一次,所有的用户线程如果结束了,守护线程自动退出,没必要进行数据备份了。

例:

package javaCoreTest;

/*
 * 守护线程
 */

public class ThreadTest14 {

	public static void main(String [] args) {
		Thread thread = new BakDataThread();
		thread.setName("备份数据的线程");
		
		//启动线程之前,将线程设置为守护线程
		thread.setDaemon(true);
		
		thread.start();
		
		//主线程:是用户线程
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "===>" + i);
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

class BakDataThread extends Thread{

	int i = 0;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		
		while(true) {
			System.out.println(Thread.currentThread().getName() + "====>" + i++);
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

12、定时器
(1)定时器的作用:
间隔特定的时间,执行特定的程序。

	每周要进行银行账户的总账操作。
	每天要进行数据的备份操作。

(2)在java中可以采用多种方式实现:

  • *可以使用sleep方法,睡眠,设置睡眠时间,每到某个时间点醒来,执行任务。这种方式是最原始的定时器(比较low)。

  • *在java类库中已经写好了一个定时器,java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在很多高级框架都是支持定时任务的。

  • *在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

例:

package javaCoreTest;

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

/*
 * 使用定时器指定定时任务
 */

public class TimerTest01 {

	public static void main(String [] args) throws ParseException {
		
		//创建定时器对象
		Timer timer = new Timer();
		//Timer timer = new Timer(true);//守护线程的方式
		
		//指定定时任务
		//timer.schedule(定时任务,第一次执行,间隔多久执行一次)
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
		Date firstDate = simpleDateFormat.parse("2021-02-19 15:40:00");
		timer.schedule(new LogTimerTask(), firstDate, 1000 * 10);
		
		//也可以用匿名内部类的方式
		/*
		 * timer.schedule(new LogTimerTask() {
		 *  public void run() {
		 * 
		 *	 }
		 * }, firstTime, period);
		 */

	}
}

class LogTimerTask extends TimerTask{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		//编写需要执行的任务
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
		String strTimeString = simpleDateFormat.format(new Date());
		System.out.println(strTimeString + "成功完成了一次数据备份!");
	}
	
}

13、实现线程的第三种方式:FutureTask方式,实现Callable接口(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。
之前的那两种方式是无法获得返回值的,因为run方法返回void。

思考:
系统委派一个线程去执行任务,该线程执行完任务后,可以会有一个执行结果,我们要怎么样才能拿到执行结果?
用实现Callable接口的方式。

例:

package javaCoreTest;

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

/*
 * 实现线程的第三种方式:
 * 	实现Callable接口
 * 
 * 这种方式的优点是:可以获取到线程执行的结果
 * 这种方式的缺点是:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
 */

public class TreadTest15 {

	public static void main(String [] args) throws Exception{
		//第一步:创建一个“未来任务类”对象
		//参数非常重要,需要给一个Callable接口实现类对象
		FutureTask task = new FutureTask(new Callable() {
			public Object call() throws Exception {
				//call()方法就相当于run方法,只不过这个有返回值
				//线程执行一个任务,执行之后可能会有一个执行结果
				//模拟执行
				System.out.println("call method begin");
				Thread.sleep(10000);
				System.out.println("call method end");
				
				int a = 100;
				int b = 200;
				return a + b;//自动装箱(300结果变成Integer)
			}
		});
		
		//创建线程对象
		Thread thread = new Thread(task);
		
		//启动线程
		thread.start();
		
		//这里是main方法,这是在主线程中
		//在主线程中,怎么获取thread线程的返回结果?
		//get方法的执行会导致“当前线程阻塞”
		Object object = task.get();
		System.out.println("线程执行结果:" + object);
		
		//main方法这里的程序要想执行必须等待get()方法的结束
		//而get方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
		//而另一个线程执行是需要时间的
		System.out.println("hello world!");
	}
}

14、关于Object类中的wait和notify方法(生产者和消费者模式)
(1)wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为他们是Object类中自带的。不通过线程对象调用。

例:t线程在o对象上活动,t 线程是当前线程对象,当调用o.wait()方法之后,t线程进入无限期等待,直到最终调用o.notify()方法,o.notify()方法的调用可以让正在o对象上等待的线程唤醒。

(2)wait方法有何作用?

Object o = new Object();
	o.wait();

表示:
让正在o对象上活动的线程进入等待状态,无期限的等待,直到被唤醒为止。
o.wait();方法的调用会让“当前线程(正在o对象上活动的线程)”进入等待状态。

(3)notify方法有何作用?

Object o = new Object();
	o. notify ();

表示:
唤醒正在o对象上等待的线程

有一个notifyAll方法:
这个方法是唤醒o对象上处于等待的所有线程。

例:

package javaCoreTest;

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

/*
 * 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个元素就表示仓库满了
 * 		如果List集合中元素个数为0,就表示仓库空了。
 * 		保证List集合中永远都是最多存储1个元素
 * 
 * 	必须达到这样的效果:生产一个消费一个。
 */

public class ThreadTest16 {

	public static void main(String [] args) {
		//创建一个仓库对象,共享的
		ArrayList 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 ArrayList list;
	
	
	public Producer(ArrayList list2) {
		super();
		this.list = list2;
	}


	@Override
	public void run() {
		// TODO Auto-generated method stub
		//一直生产(使用死循环)
		while(true) {
			//给仓库list对象加锁
			synchronized (list) {
				if(list.size() > 0) {
					try {
						//当前线程进入等待状态,并释放list集合的锁

						list.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				//如果程序能够执行到这里,说明仓库是空的,可以生产
				Object object = new Object();
				list.add(object);
				System.out.println(Thread.currentThread().getName() + "===>" + object);
				
				//唤醒消费者消费
				list.notify();
			}
		}	
	}	
}

//消费线程
class Consumer implements Runnable{

	//仓库
	private ArrayList list;
	
	
	public Consumer(ArrayList list2) {
		super();
		this.list = list2;
	}


	@Override
	public void run() {
		// TODO Auto-generated method stub
		//一直消费
		while(true) {
			synchronized (list) {
			
				if(list.size() == 0) {
					try {
						list.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				//程序能够执行到此处,说明仓库中有数据,进行消费。
				Object object = list.remove(0);
				System.out.println(Thread.currentThread().getName() + "---->" + object);
				
				//唤醒生产者生产
				list.notify();
			}
		}
	}
	
}

实现一个账户的取款:

package javaCoreTest.threadSafe;

public class Test {

	public static void main(String [] args) {
		//创建账户对象(只创建1个)
		Account act = new Account("act-001", 10000);
		
		//创建两个线程
		Thread t1 = new AccountThread(act);
		Thread t2 = new AccountThread(act);
		
		//设置name
		t1.setName("t1");
		t2.setName("t2");
		
		//启动线程取款
		t1.start();
		t2.start();
	}
}

//账户类
//没有线程同步,会出现安全问题
class Account {

	//账号
	private String actnoString;
	
	//余额
	private double balance;
	
	//对象
	Object obj1 = new Object();

	public Account(String actnoString, double balance) {
		this.actnoString = actnoString;
		this.balance = balance;
	}

	public Account() {
		// TODO Auto-generated constructor stub
	}

	public String getActnoString() {
		return actnoString;
	}

	public void setActnoString(String actnoString) {
		this.actnoString = actnoString;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
	
	//取款的方法
	/*
	 * 在实例方法上可以使用synchronized吗?可以
	 * synchronized出现在实例方法上,一定锁的是this,不能是其他对象了。
	 * 这种方式不灵活。
	 * 
	 * 另外一个缺点:synchronized出现在实例方法上,表示整个方法体都需要同步,
	 * 可能会无故扩大同步的范围,导致程序的执行效率降低。
	 * 
	 * 这种方式不常用。
	 */	
	public synchronized void withdraw(double money) {

			double before = this.getBalance();

			double after = before - money;

			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			//更新余额
			this.setBalance(after);
		}
	}	

//账户线程
class AccountThread extends Thread{

	//两个线程必须共享同一个账号对象
	private Account act;

	//通过构造方法传递过来账号对象
	public AccountThread(Account act) {
		this.act = act;
	}

	@Override
	public void run() {
		//run方法的执行表示取款操作
		//假设取款5000
		double money = 5000;
		
		//取款
		//多线程并发执行这个方法
		//synchronized (act) {//这种方式也可以,只不过扩大了同步的范围,效率变低了
			act.withdraw(money);
		//}
		
		
		System.out.println(Thread.currentThread().getName() + "账户" + act.getActnoString() + "取款成功,余额" + act.getBalance());
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值