带妹学Java第二十天(线程完整篇)

知识点

1.线程概述

1.1 什么是线程
 线程是程序执行的一条路径, 一个进程中可以包含多条线程
 一个应用程序可以理解成就是一个进程
 多线程并发执行可以提高程序的效率, 可以同时完成多项工作

1.2 多线程应用场景
 VNC同时共享屏幕给多个电脑
 迅雷开启多条线程一起下载
 QQ同时和多个人一起视频
 服务器同时处理多个客户端请求

1.3并行和并发的区别
 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于间时间隔较短,使人感觉两个任务都在运行(画图-任务调度)。
在这里插入图片描述
在这里插入图片描述
1.4 Java程序运行原理
 Java命令会启动java虚拟机(JVM),等于启动了一个应用程序,也就是启动了一个进程。
 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
 一个应用程序有且只有一个主线程,程序员不能New主线程,可以New子线程。

1.5 JVM启动的是多线程吗?
 JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
 main方法的代码执行的位置就是在主线程(路径)
 一个进程有多个线程
 finalize()这个方法在子线程(垃圾回收线程)执行

public class Demo01 {
	public static void main(String[] args) {
/*JVM的启动是多线程的吗?【面试题】*/
		
		System.out.println("AAAAA");
		System.out.println("BBBBB");
		System.out.println("CCCCC");
		System.out.println("DDDDD");
		
		//打印线程名称
		System.out.println(Thread.currentThread());//主线程
		
		for(int i = 0;i<2;i++){
			new Student();
			System.gc();//启动垃圾回收
		}
	}
}
class Student{
	//被垃圾回收器回收时,会调用
	//对象从内存释放时,会调用 
	@Override
	protected void finalize() throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("student 被回收了...");
		//打印线程名称
		System.out.println(Thread.currentThread());//子线程
	}
}

2.Java中线程的实现方式(重点)

2.1方式一、继承Thread
使用步骤:
1.定义类继承Thread
2.重写run方法
3.把新线程要做的事写在run方法中
4.创建线程对象
5.开启新线程, 内部会自动执行run方法

public class Demo01 {
	public static void main(String[] args) {
		/*主线程,程序员不能创建,程序员只能创建子线程*/
		
		//1.创建子线程对象
		MyThread t1 = new MyThread();
		
		/**不能通过下面的方式来执行任务
		 * 因为这种试的任务是在主线程执行的*/
		//t1.run();
		
		//2.正确的执行任务的方式,调用start,内部会开启新线程,调用run方法
		t1.start();
		
		//3.再创建子线程
		MyThread t2 = new MyThread();
		t2.start();
				
		//4.循环创建子线程
		for(int i=0;i<10;i++){
			MyThread th = new MyThread();
			th.start();
		}
		
	}

}

class MyThread extends Thread{
	
	@Override
	public void run() {
		System.out.println("银行信用卡还款短信任务..." + Thread.currentThread());
	
		System.out.println("线程名称" + this.getName());
	}
}


2.2方式二、实现Runnable接口
实现步骤:
1.定义类实现Runnable接口
2.实现run方法
3.把新线程要做的事写在run方法中
4.创建自定义的Runnable的子类对象,创建Thread对象传入Runnable
5.调用start()开启新线程, 内部会自动调用Runnable的run()方法

public class Demo01 {
	public static void main(String[] args) {
/*		线程实现的方式 (2) - 定义类实现Runnable接口
		//1.创建runable对象
		BankTask task = new BankTask();
		
		//2.创建Thread对象
		Thread t1 = new Thread(task);
		
		//3.启动线程
		t1.start();
		
		//4.再开启2个线程
		Thread t2 = new Thread(task);
		t2.start();
		
		Thread t3 = new Thread(task);
		t3.start();
	}
}

class BankTask  implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("银行储蓄卡自动结算利息任务..." + Thread.currentThread());
		
		//System.out.println("线程名称:" + this.getName());
		System.out.println("线程名称:" +Thread.currentThread().getName());
	}
	
}

2.3两种方式的区别
区别:
 继承Thread : 由于子类重写了Thread类的run(), 当调用start()时直接找子类的run()方法
 实现Runnable : 构造函数中传入了Runnable的引用, 有个成员变量记住了它, 调用run()方法时内部判断成员变量Runnable的引用是否为空。
在这里插入图片描述
继承Thread
 好处是:可以直接使用Thread类中的方法,代码简单
 弊端是:如果已经有了父类,就不能用这种方法
实现Runnable接口
 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,代码更灵活
 弊端是:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂
在这里插入图片描述
在这里插入图片描述
2.4 匿名内部类实现线程的两种方式

public static void main(String[] args) {
		//匿名内部类实现线程的两种方式		
		/*Thread t1 = new Thread(){
			@Override
			public void run() {
				System.out.println("任务1...." + Thread.currentThread());
			}
		};
		t1.start();*/
		
		new Thread(){
			public void run() {
				System.out.println("任务1...." + Thread.currentThread());
			};
		}.start();
		
		
		/*Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("任务2...." + Thread.currentThread());
			}
		});
		t2.start();*/
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("任务2...." + Thread.currentThread());
			}
		}).start();
	}

2.5 获取线程名字和设置名字
 通过Thread的getName()方法获取线程对象的名字
 通过setName(String)方法可以设置线程对象的名字
 通过构造函数可以传入String类型的名字
 每个线程系统都会默认分配个名字,主线程:main,子线程thread-0 …

public class Demo01 {
	public static void main(String[] args) {
/*		获取线程名字和设置名字(掌握) 
		//1.获取主线程对象
		Thread mainThread = Thread.currentThread();
		System.out.println(Thread.currentThread());
		System.out.println(mainThread);
		System.out.println("名称:" + mainThread.getName());
		
		//2.设置线程的名称
		mainThread.setName("主线程");
		System.out.println(mainThread);
		
		//3.设置子线程的名称
		MyThread myThread = new MyThread("子线程1");
		myThread.start();
	}
}

class MyThread extends Thread{
	
	public MyThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		System.out.println("银行代发工资任务..." + Thread.currentThread());
	}
}
 

在这里插入图片描述
2.6 获取当前线程的对象
 Thread.currentThread()方法用于获取当前线程对象
 在不同的方法中,获取的线程对象名称是有可能不一样的
 在main中获取的是主线程对象
 在子线程的run方法中获取的是子线程对象

public class Demo01 {

	public static void main(String[] args) {
		//获取当前线程的对象(掌握)
		Thread mainThread = Thread.currentThread();
		mainThread.setName("主线程");
		//打印主线程对象
		System.out.println(mainThread);
		
		//打印主线程对象类名
		System.out.println(mainThread.getClass());
		
		System.out.println("================");
		//开启子线程
		MyThread mt = new MyThread();
		mt.start();
	}
}

class MyThread extends Thread{
	@Override
	public void run() {
		System.out.println("任务...");
		Thread subThread = Thread.currentThread();
		//打印子线程对象
		System.out.println(subThread);
		//打印子线程对象类名
		System.out.println(subThread.getClass().getName());
		
	}
}
 

在这里插入图片描述

3.线程的其它方法

3.1线程休眠(掌握)

 Thread.sleep(毫秒), 控制当前线程休眠若干毫秒
 1秒= 1000毫秒
 1秒 = 1000毫秒* 1000微妙 * 1000纳秒(1000000000 )
主线程休眠

/*** 主线程休眠 */
	public static void test1() {
		for(int i=0;i<10;i++){
			System.out.println(i);
			//休眠【暂停】
			try {
				Thread.sleep(1000);//主线程休眠
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("AAAAAAAAAAAAAAAAAA");
	}

子线程休眠

/**
	 * 子线程休眠
	 */
	public static void test2() {
		//子线程休眠
		new Thread(){
			public void run() {
				for(int i=0;i<10;i++){
					System.out.println(Thread.currentThread() + " " + i);
					//休眠
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		

		System.out.println("AAAAAAAAAAAAAAAA");
	}

3.2守护线程(了解)

 setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
 特点:男守护女,女的死,男的也不想活了

3.3加入线程(了解)

 join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
 join(int), 可以等待指定的毫秒之后继续

3.4线程让出(了解)

 yield() 让出cpu

3.5线程优先级

 setPriority()设置线程的优先级
 默认优先级是5,最小优先级1,最高优先级10
 可以设置2,3,4
 Thread里面有静态常量
 开发几乎不用,了解
在这里插入图片描述

4.线程与同步(重点)

什么是同步
 同步就是加锁,不让其它人访问
 synchronized指的就是同步的意思
什么情况下需要同步
 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
同步代码块
 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
在这里插入图片描述
同步方法
 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
 非静态同步函数的锁是:this
 静态同步函数的锁是:字节码对象(xx.class)

5.锁的总结

1.锁问题:

同步中,锁最好同一个对象,如果不是同一对象,还是会有线程安全问题
锁:this,代表当前对象
锁:如果 new 对象,就不是同一把锁
锁:字节码对象 String.class,内存中,只有一个字节码对象
开发中:一般都是this

2.在方法内部声明synchronized的就是 “同步代码块”

3.在声明方法的时候,添加 synchronized,就是同步方法
》如果是非静态方法,锁就是this
》如果是静态方法,锁就当前类的字节码对象

//TicketTask.class
public static synchronized void test1(){}

4.同步使用的建议:

同步加锁的时候,尽量让锁住的代码范围小一点,这样可以让其它线程等待时间少一点,性能高

6.死锁

 死锁就是大家都抱着锁,不释放

public class Demo01 {

	private static String s1 = "筷子左";
	private static String s2 = "筷子右";
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		//死锁(了解)
		//多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
		
		new Thread(){
			public void run() {
				while(true){
					synchronized (s1) {
						System.out.println("线程A 拿到" + s1 + " 等待" + s2);
						synchronized (s2) {
							System.out.println("线程A 拿到" + s2 + " 开动吃饭...");
						}
					}
				}
				
			};
		}.start();
		
		new Thread(){
			public void run() {
				while(true){
					synchronized (s2) {
						System.out.println("线程B 拿到" + s2 + " 等待" + s1);
						synchronized (s1) {
							System.out.println("线程B 拿到" + s1 + " 开动吃饭。。");
						}
					}
				}
			};
		}.start();
	}

}

7.回顾线程安全的类

 Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
 Vector是线程安全的,ArrayList是线程不安全的
 StringBuffer是线程安全的,StringBuilder是线程不安全的
 Hashtable是线程安全的,HashMap是线程不安全的

8.单例设计模式(重点)

8.1什么是单例

 保证类在内存中只有一个对象。
 对象是new出来的,因此也就是说在程序中只能new一次对象

8.2单例实现的基本步骤

1》声明一个类,类中有一个静态属性,类型与类名相同
2》把空参构造方法声明为私有
3》在类中提供一个公共静态访问方法来返回该对象实例

8.3单例的多种写法

写法一 饿汉式

class Singleton{
	private static Singleton instance = new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
		return instance;
	}
}

写法二 懒汉式

class Singleton{
	private static Singleton instance;
	
	private Singleton(){}
	
	public static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

写法三 另一种简单

class Singleton{
	public static final Singleton instance = new Singleton();
	private Singleton(){}
}

8.4饿汉式和懒汉式的区别

 饿汉式是空间换时间,懒汉式是时间换空间
 在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
 如果考虑线程安全问题,用饿汉式
 如果不考虑线程安全问题,用懒汉式

8.5Runtime类的使用

 Runtime类是一个单例类
 每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。通过 getRuntime 方法获取当前运行时。
 案例:自动关机
Runtime r = Runtime.getRuntime();
r.exec(“shutdown -s -t 300”);//300秒后关机
r.exec(“shutdown -a”); //取消关机

9. Timer定时器

 Timer一种工具,用于在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
 方法
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task, long delay, long period)
public void schedule(TimerTask task, Date firstTime, long period)

public class Demo01 {

	public static void main(String[] args) {
		//Timer(计时器,定时器)
/*		一种工具,用于在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
		方法
		public void schedule(TimerTask task, long delay)
		public void schedule(TimerTask task, long delay, long period) 
		指定时间执行任务
		public void schedule(TimerTask task, Date firstTime, long period)*/

		
		test3();

	}

	public static void test3() {
		/**定时器的细节
		 * 1.定时器在子线程中执行
		 * 2.timer.cancel(); 取消定时器
		 */
		
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			int count = 5;
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("任务A:" + count +"..." + Thread.currentThread());
				count --;
				if(count == 0){
					//取消定时器
					timer.cancel();
				}
			}
		}, 1000,2000);
		
		//timer.cancel();//主线程
	}

	public static void test2() {
		//3秒后执行任务,每隔两秒重复执行任务
		Timer timmer = new Timer();
		timmer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("任务A....." + new Date());
				
			}
		}, 3000, 2000);
	}

	//3秒后执行任务
	public static void test1() {
		//1.创建定时器
		Timer timmer = new Timer();
		
		//2.执行任务
		/**
		 * 1.3秒后执行任务
		 * 2.任务执行完后,程序没有退出
		 */
		timmer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("任务A....");
				
			}
		}, 3000);
	}
}

10.线程间的通讯(重点)

10.1什么时候需要通信

多个线程并发执行时, 在默认情况下CPU是随机切换线程的,如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印

10.2线程怎么通信

》如果希望线程等待, 就调用wait()
》如果希望唤醒等待的线程, 就调用notify();
notify是随机唤醒一个线程
notifyAll是唤醒所有线程
》这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
》如果方法中没有同步锁,会有异常IllegalMonitorStateException

10.3线程通讯的一些疑问

1.在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法
2.为什么wait方法和notify方法定义在Object这类中?
因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中
3.sleep方法和wait方法的区别?
》sleep方法必须传入参数,参数就是时间,时间到了自动醒来
》wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
》sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡
》wait方法在同步函数或者同步代码块中,释放锁

11.JDK1.5新特性互斥锁(重点)

11.1ReentrantLock介绍

 使用ReentrantLock类也可以实现同步加锁
 ReentrantLock叫[互斥锁],使用lock()和unlock()方法进行同步

11.2使用ReentrantLock类使用要点

 使用ReentrantLock类的newCondition()方法可以获取Condition对象
 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

12.线程组

12.1概述

1.Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
2.默认情况下,所有的线程都属于主线程组。
3.public final ThreadGroup getThreadGroup() 通过线程对象获取他所属于的组
4.public final String getName() 通过线程组对象获取组的名字
5.我们也可以给线程设置分组ThreadGroup(String name) 创建线程组对象并给其赋值名字

12.2创建线程对象

Thread(ThreadGroup?group, Runnable?target, String?name)

12.3代码演示


/**
 * 掌握:
 * 1.如何获取一个线程所属的线程组
 * 2.如果在创建一个子线程时,设置它所属的线程组
 * @author gyf
 *
 */
public class Demo01 {
	public static void main(String[] args) {
		//主线程
		Thread mainThread = Thread.currentThread();
		/**
		 * [main,5,main]
		 * main:线程名称
		 * 5:代先级
		 * main:当前线程所属的组名
		 */
		System.out.println("线程:" + mainThread);
		
		//获取线程的“线程组”对象
		ThreadGroup tg = mainThread.getThreadGroup();
		System.out.println("线程组:" + tg.getName());

		
		//创建子线程
		Thread t1 = new Thread(){
			@Override
			public void run() {
				System.out.println("线程A...");
			}
		};
		//t1.start();
		System.out.println("t1子线程的线程组:" + t1.getThreadGroup());
		
	   //创建一个线程组
		ThreadGroup abcGroup = new ThreadGroup("abc组");
		//创建子线程对象
		Thread t2 = new Thread(abcGroup, new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("线程B");
			}
		});
		System.out.println("t2子线程的线程组:" + t2.getThreadGroup());
	}
}


13.线程池

13.1线程池概述

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

13.2Java的内置线程池

1.JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()

  1. 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,
    可以执行Runnable对象或者Callable对象代表的线程。
    它提供了如下方法
    Future<?> submit(Runnable task)
    Future submit(Callable task)

3.使用步骤:
1.创建线程池对象
2.创建Runnable实例
3.提交Runnable实例
4.关闭线程池es.shutdown();

4.Runnable和Callable的区别
Runnable的run方法没有返回值
Callable的call方法有返回值,一般返回值也没用

13.3使用演示

public class Demo01 {
	public static void main(String[] args) {
		//案例:10个线程完成10个洗车任务
		/*for(int i = 0;i<10;i++){
			new Thread(){
				public void run() {
					System.out.println("洗车任务 " + Thread.currentThread());
				};
			}.start();
		}*/
		
		//案例:5个线程完成10个洗车的任务
		//1.创建线程池
		ExecutorService es = Executors.newFixedThreadPool(5);
		
		//2.添加任务-方式一
		/*for(int i=0;i<10;i++){
			es.submit(new Runnable() {
				@Override
				public void run() {
					System.out.println("洗车任务 " + Thread.currentThread());
				}
			});
		}*/
		
		//3.添加任务-方式二
		for(int i=0;i<10;i++){
			es.submit(new MyTask());
		}
		
	}
}

class MyTask implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		System.out.println("洗车任务 " + Thread.currentThread());
		return 110;
	}
	
}


14.线程的五种状态

 新建,就绪,运行,阻塞,死亡
在这里插入图片描述

练习题

1.卖火车票

 需求,有A\B\C\D4个窗口同时买票,只有100张票可买
多线程会有安全问题熟记

public class Demo01 {

	public static void main(String[] args) {
		//同步代码块和同步方法
		
		//火车站卖票【问题】
		/**
		 * 湖南到广州火车票:今天13:00 ,100张
		 * 火车站有4个窗口在同时卖票,要保证一张票只能被卖一次
		 * 
		 * 搞4个线程表示4个窗口
		 * 
		 * 通过加锁可以解决被多次卖同一张票的问题
		 * 
		 * 使用同步代码块
		 */
		
		//创建卖票的任务
		TicketTask task = new TicketTask();
		
		//A窗口
		Thread t1 = new Thread(task);
		t1.setName("窗口A");
		//B窗口
		Thread t2 = new Thread(task);
		t2.setName("窗口B");
		//C窗口
		Thread t3 = new Thread(task);
		t3.setName("窗口C");
		//D窗口
		Thread t4 = new Thread(task);
		t4.setName("窗口D");
		
		//开启线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class TicketTask implements Runnable{
	//只有100张票
	int ticket = 100;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		/**
		 * 同步代码换括号里参数可以传任意对象
		 * this是一个锁对象
		 * 不同的一把锁,卖相同的票总是还是存在
		 */
		//卖票
		while(true){
			synchronized (this) {
				if(ticket <= 0){
					System.out.println("不好意思,票已经卖完了...");
					break;
				}else{
					System.out.println(Thread.currentThread() + "恭喜你卖到票,票号" + ticket);
					ticket --;
				}
			}
			
		}
	}
	
	/*@Override
	public void run() {
		// TODO Auto-generated method stub
		*//**
		 * 同步代码换括号里参数可以传任意对象
		 *//*
		synchronized (this) {
			//卖票
			while(true){
				if(ticket <= 0){
					System.out.println("不好意思,票已经卖完了...");
					break;
				}else{
					System.out.println(Thread.currentThread() + "恭喜你卖到票,票号" + ticket);
					ticket --;
				}
			}
		}
	}*/
}

2.两个线程间的通讯

public class Demo01 {
	public static void main(String[] args) {
		//1.创建任务对象
		MyTask task = new MyTask();
		
		//2.开启两个线程执行2个任务
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task1();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task2();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
	}
}

class MyTask{
	
	//标识 1:可以执行任务1,2:可以执行任务2
	int flag = 1;
	
	public synchronized void task1() throws InterruptedException{
		if(flag != 1){
			this.wait();//当前线程等待
		}
		
		System.out.println("1.银行信用卡自动还款任务...");
		flag = 2;
		this.notify();//唤醒其它线程
		
	}
	
	public synchronized void task2() throws InterruptedException{
		
		if(flag != 2){
			this.wait();//线程等待
		}
		
		System.out.println("2.银行储蓄卡自动结算利息任务...");
		flag = 1;
		this.notify();//唤醒其它线程
	}
}


3.三个线程间的通讯(同步锁实现有问题,少数没按顺序)

public class Demo01 {
	public static void main(String[] args) {
		//三个线程间的通讯
		MyTask task = new MyTask();
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task1();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task2();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task3();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
	}
}

class MyTask{
	
	//标识 1:可以执行任务1,2:可以执行任务2, 3:可以执行任务3 
	int flag = 1;
	
	public synchronized void task1() throws InterruptedException{
		if(flag != 1){
			this.wait();//当前线程等待
			//this.wait(timeout);
		}
		
		System.out.println("1.银行信用卡自动还款任务...");
		flag = 2;
		//this.notify();//唤醒随机线程
		this.notifyAll();//唤醒所有等待线程
		
	}
	
	public synchronized void task2() throws InterruptedException{
		
		if(flag != 2){
			this.wait();//线程等待
		}
		
		System.out.println("2.银行储蓄卡自动结算利息任务...");
		flag = 3;
		//this.notify();//唤醒其它线程
		this.notifyAll();
		//Thread.sleep(millis);
	}
	
	public synchronized void task3() throws InterruptedException{
			if(flag != 3){
				this.wait();//线程等待
			}
			
			System.out.println("3.银行短信提醒任务...");
			flag = 1;
			//this.notify();//唤醒其它线程
			this.notifyAll();
	}
}

4.三个线程间的通讯(使用互斥锁,解决同步锁少数没按顺序的问题)

/**
 * 互斥锁的使用步骤
 * 1.创建互斥锁对象
 * 2.创建3个Condition
 * 3.加锁、解锁
 * 4.线程等待-Condition的await方法
 * 5.线程唤醒-Condition的signal方法
 * @author gyf
 *
 */
class MyTask{
	//创建互斥锁对象
	ReentrantLock rl = new ReentrantLock();
	//创建3个Condition
	Condition c1 = rl.newCondition();
	Condition c2 = rl.newCondition();
	Condition c3 = rl.newCondition();
	
	//标识 1:可以执行任务1,2:可以执行任务2, 3:可以执行任务3 
	int flag = 1;
	
	public void task1() throws InterruptedException{
	 rl.lock();//加锁
			if(flag != 1){
				c1.await();//当前线程等待
			}
			
			System.out.println("1.银行信用卡自动还款任务...");
			flag = 2;
			
			//指定唤醒线程2
			c2.signal();
	 rl.unlock();//解锁
	}
	
	public void task2() throws InterruptedException{
	  rl.lock();	
			if(flag != 2){
				c2.await();//线程等待
			}
			
			System.out.println("2.银行储蓄卡自动结算利息任务...");
			flag = 3;
			
			//唤醒线程3
			c3.signal();
	  rl.unlock();
	}
	
	public void task3() throws InterruptedException{
	 rl.lock();
			if(flag != 3){
				c3.await();//线程等待
			}
			
			System.out.println("3.银行短信提醒任务...");
			flag = 1;
			
			//唤醒线程1
			c1.signal();
	 rl.unlock();
	}
}

总结

通过线程一天的学习,会使用了两种创建线程的方法,一种是继承Thread类,另一种是实现Runnable接口(两种方法都可以使用匿名内部类直接创建)。然后就是线程的其他方法,例如休眠、守护、加入等。掌握了同步代码块、同步方法关键字synchronized,在使用同步做案例:三个线程任务时,发现顺序会出问题,于是学习了互斥锁ReentrantLock,有效的实现了多线程调度的有序性。后面对线程池有了了解。记住,多线程会有安全问题。

发布了24 篇原创文章 · 获赞 4 · 访问量 607

猜你喜欢

转载自blog.csdn.net/qq_43488797/article/details/103948826