Java 之 Java 多线程编程

多线程编程

进程与线程

进程:一个正在运行的程序(独立运行的程序)
一个进程中可以只有一个线程	单线程程序
一个进程中可以有多个线程	多线程程序

线程:一个线程相当于一个 cpu 的执行路径(大大提升了处理效率)
	一个独立的运行单元
创建一个线程,相当于 CPU 开辟了一个独立的运行执行路径
每个执行路径都是一个独立空间
创建一个线程,该线程就会拥有一个独立的栈空间
如果在同一个栈空间,不符合先入栈的后出栈这个规则
**注意:start 方法和 run 方法的区别**
start 方法调用的是一个线程
run 方法调用的是一个普通的方法
标准的单线程程序
public class Demo {
	public static void main(String[] args) {
		print();
		printRemove();
		System.out.println("程序执行完毕~");
	}
	public static void print() {
		for (int i = 0; i < 100; i++) {
			System.out.println(i);
		}
	}
	public static void printRemove() {
		System.out.println("删除");
	}
}
绝对安全,程序由上至下依次执行,但是效率不高

这里写图片描述

这里写图片描述

多线程实现的三种方式

多线程实现过程

JVM 调用 main 方法 ---> 找操作系统(CPU) ---> 开启一个执行路径
开启了一个叫 main 执行路径, main 就是一个线程, main 是线程的名字,又叫主线程
线程默认名字:Thread-0,Thread-1.....
继承 Thread 类
Thread 方法
方法名方法描述
public String start()使该线程开始执行;JVM 调用该线程的 run 方法
public String run()调用该线程的 run 方法
public final void setName(String name)改变线程名称,使之与参数 name 相同
1.继承 Thread 类
2.重写父类的 run 方法

public class Demo {
	public static void main(String[] args) {
		// 创建一个线程
		// 两种对线程进行命名的方法
		SubThread subThread = new SubThread("线程1");
		subThread.setName("线程");
		/*
		 * 直接调用 run 方法,相当于就调用了一个普通成员方法
		 * subThread.run();
		 */
		// 获取主线程的名字
		// 获取当前执行的线程对象,是一个静态方法
		Thread currentThread = Thread.currentThread();
		// 获取主线程名字
		String name = currentThread.getName();
		// 开启线程的方法
		// 注意:直接调用 run 方法不能开启线程,需要调用 start 方法开启线程
		subThread.start();
		for (int i = 0; i < 50; i++) {
			System.out.println(name + "---main 方法---" + i);
		}
		
	}
}
// Thread(String name) 该方法在创建线程的同时给线程起个名
// 构造方法不能被继承,要想使用父类的构造方法,可以在子类中写带参数的构造方法
class SubThread extends Thread {
	public SubThread() {
		super();
	}
	public SubThread(String name) {
		// 调用父类的有参构造方法
		super(name);
	}
	// 重写 run 方法
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			// 获取线程名的方法 getName
			System.out.println(Thread.currentThread().getName() + "---run 方法---" + i);
		}
	}
}
**注意:在继承 Thread 时,不要对成员变量命名为 name!**
class NameThread extends Thread {
	private String name;
	// 构造
	public NameThread() {
		super();
	}
	public NameThread(String name,String myName) {
		// 传递的是线程的名字,利用的是父类的构造方法
		super(name);
		// 给自己类的 name 赋值
		// 建议:不要使用 name 这个名字作为成员变量名,避免不必要的麻烦
		this.name = myName;
	}
	/*
	 * 父类已经有了 getName() 方法,并且用 final 修饰,不能被重写,所以报错
	 * public String getName() {
	 * 	   return name;
	 * }
	 * public void setName(String name) {
	 *     this.name = name;
	 * }
	 * 解决方法:不使用标准 get/set 方法,修改 get/set 方法名
	 */
	public String getNewName() {
		return this.name;
	}
	public void setNewName(String newName) {
		this.name = newName;
	}
	@Override
	public void run() {
		super.run();
	}
}
实现 Runnable 接口
Java 中,只能单继承,当该类已经继承别的类的时候,不能继承 Thread 接口,因此使用实现 Runnable 接口来实现多线程
public class Demo {
	public static void main(String[] args) {
		// 创建线程
		RunableImpl runableImpl = new RunableImpl();
		Thread thread = new Thread(runableImpl);
		// 开启线程
		thread.start();
	}
}
// 接口中 run 方法是一个抽象方法
class RunableImpl implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + "(" + i + ")");
		}
	}
}
匿名内部类方式
相当于创建一个该类的子类对象

定义:
new 父类名或接口名(){
	重写父类或实现接口的方法
};
这里 new 出来的就是这个类的子类对象
class Test{
	public void fun(){
		System.out.println("父类 fun() 方法");
	}
}
// 创建 Test 的子类对象
new Test(){
	// 重写父类的方法
	@Override
	public void fun(){
		System.out.println("子类的 fun()方法");
	}
}.fun();// 这里调用的是子类的 fun()方法

// 使用多态来接收子类
Test test = new Test(){
	public void fun(){
		System.out.println("子类的 fun()方法");
	};
};
// 调用子类 fun()方法
test.fun();
interface TestInter {
	public abstract void fun();
}
// 创建接口的实现类
TestInter testInter = new TestInter() {
	@override
	public void fun(){
		System.out.println("实现类的 fun()方法");
	}
};
testInter.fun();
利用匿名内部类方式,给 TreeSet 集合中的学生对象,按年龄排序
学生:姓名,年龄
/*
 * 学生类
 */
public class Student {
	private String name;
	private int age;
	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	@Override
	public String toString() {
		return "姓名:" + name + "\n 年龄:" + age;
	}
}
public class Demo{
	public static void main(String[] args){
		TreeSet<Student> set = new TreeSet<>(new  Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				return o1.getAge() - o2.getAge();
			}
		});
		set.add(new Student("a", 80));
		set.add(new Student("b", 40));
		set.add(new Student("c", 60));
		set.add(new Student("d", 10));
		set.add(new Student("e", 20));
		for (Student student : set) {
			System.out.println(student);
		}
	}
}			
使用匿名内部类方式创建线程(两种方式)
new Thread(){
	@Override
	public void run(){
		for(int i = 0; i < 50; i++){
			System.out.println(Thread.currentThread().getName() + "(" + i + ")");
		}
	}
}.start();


Runnable runnable = new Runnable() {
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + "(" + i + ")");
		}
	}
};
// 放入线程对象中
Thread thread = new Thread(runnable);
thread.start();

new Thread(new Runnable() {
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + "(" + i + ")");
		}
	}
}).start();
**接口实现创建线程的好处**
1.避免直接继承 Thread 类的局限性(避免单继承)
2.接口即插即用,减少类与类之间的联系(可以解耦)

线程的六种状态

1.新建状态(new 线程对象)
2.运行状态(调用 start()方法)
3.阻塞状态(等待 CPU 的执行资源)
4.休眠状态(调用 sleep(时间)方法)
5.等待状态(调用了 wait()方法)
6.死亡状态(run() 方法执行完毕)

这里写图片描述

线程休眠

// 线程在 main 方法中休眠时,可以把异常抛出
public static void main(String[] args) throws InterruptedException {
	for (int i = 0; i < 100; i++) {
		// 线程休眠1秒 单位毫秒
		Thread.sleep(1000);
		System.out.println(Thread.currentThread().getName() + "\t(" + i);
	}
}
/*
 * 在子线程中,出现异常,只能 try...catch 处理
 * Thread 类是 Runnable 接口的实现类
 * 重写接口中 run 方法
 * 该方法没有抛出异常,所以所有 Runnable 接口的实现类(包括 Thread 类)都不能再 run 方法中抛出异常
 * 只能 try...catch 处理
 */
class SleepThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			// 休眠一秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "\t(" + i);
		}
	}
}

同步锁

synchronized 关键字
两个或者两个以上的并发线程访问同一个对象的时候,就会用到同步代码块
在同一时间内,只能有一个线程访问这个对象,其他线程在同步代码块外被阻塞

同步锁(同步代码块)
锁可以是任意对象,但是要保证锁的唯一,多个线程使用的是同一把锁
synchronized(锁){

}
同步锁规则:
线程遇到锁就进同步代码块(并且携带锁)
当线程执行完代码块中的代码,把锁返还
线程没有遇到锁会在同步代码块外等待,遇到锁才能进
/*
 * 卖火车票问题
 */ 
public class Demo {
	public static void main(String[] args) {
		TicketRunnable ticketRunnable = new TicketRunnable();
		// 创建3个线程这些线程会执行 run 方法(线程内的任务)
		Thread thread = new Thread(ticketRunnable);
		Thread thread2 = new Thread(ticketRunnable);
		Thread thread3 = new Thread(ticketRunnable);
		// 开启三个 线程
		thread.start();
		thread2.start();
		thread3.start();
	}
}

class TicketRunnable implements Runnable{
	// 声明票,同时私用票这个成员变量(保证票是共享数据,只 new 一次该类对象)
	private int tickets = 50;
	// 创建对象锁,保证唯一,确保多个线程,卖的是一份票,而不是每个线程卖不同份的同样份数的票
	private Object object = new Object();
	// 卖票方法
	@Override
	public void run() {
		while (true) {
			// 锁只要保证是对象和唯一就可以
			synchronized (object) {
				// 操作的共享数据的代码
				if (tickets > 0) {
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						// TODO: handle exception
					}
					// 有票就卖
					System.out.println(Thread.currentThread().getName() + "剩余票数:" + tickets);
					// 减少一张
					tickets--;
				} else {
					// 没票 结束循环
					break;
				}
			}
			// 让线程让出CPU的资源(不代表,下次执行卖票的线程不是这个线程,让出后,三个线程又开始进入同步锁,随机进入)
			Thread.yield();
		}
	}
}
public class Demo {
	public static void main(String[] args) {
		TicketRunnable1 ticketRunnable1 = new TicketRunnable1();
		// 创建3个线程这些线程会执行 run 方法(线程内的任务)
		Thread thread = new Thread(ticketRunnable1);
		Thread thread2 = new Thread(ticketRunnable1);
		Thread thread3 = new Thread(ticketRunnable1);
		// 开启三个 线程
		thread.start();
		thread2.start();
		thread3.start();
	}
}
class TicketRunnable1 implements Runnable{
	// 声明票(保证票是共享数据,只 new 一次该类对象)
	private int tickets = 50;
	// 卖票方法
	@Override
	public void run() {
		while (true) {
			if (sellTickets()) {
				break;
			}
			// 让线程让出CPU的资源
			Thread.yield();
		}
	}
	// 操作共享数据的方法
	// 在方法中添加 synchronized 关键字 把方法变成同步方法
	public synchronized boolean sellTickets() {
		// 静态方法的同步代码的锁,可以使用本类 类名. class
		//(可以使用静态方法,注意,静态方法只能调用静态方法或者静态变量
		// 当改为静态时,要注意把相关的方法和成员变量也一起改为静态)
		// 操作的共享数据的代码
		if (tickets > 0) {
			// 有票就卖
			System.out.println(Thread.currentThread().getName() + "剩余票数:" + tickets);
			// 减少一张
			tickets--;
			return false;
		} else {
			// 没票 结束循环
			return true;
		}
	}
}
/*
 * 公司年会 进入公司有两个门(前门和后门)
 * 进门的时候 每位人都能获取一张彩票(7位数)
 * 公司有100个员工
 * 利用多线程模拟进门过程
 * 统计每个入口入场的人数 
 * 每个人拿到的彩票的号码 要求7位数字 不能重复
 * 打印格式:
 * 编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是: [17, 24, 29, 30, 31, 32, 07]
 * 编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是: [06, 11, 14, 22, 29, 32, 15]
 * .....
 * 从后门 入场的员工总共: 45 位员工
 * 从前门 入场的员工总共: 55 位员工
 */
 public class Demo {
	public static void main(String[] args) {
		// 创建线程
		YHRunnable yhRunnable = new YHRunnable();
		Thread thread = new Thread(yhRunnable, "前门");
		Thread thread1 = new Thread(yhRunnable, "后门");
		thread.start();
		thread1.start();
	}
}
class YHRunnable implements Runnable {
	// 操作的共享数据 100人
	private int pNum = 100;
	// 记录从前门入门的人数
	private int qNum = 0;
	// 记录从后门入门的人数
	private int hNum = 0;
	@Override
	public void run() {
		// 进门
		while (true) {
			synchronized (this) {
				// 结束循环条件
				if (pNum <= 0) {
					break;
				}
				// 获取进入的线程的名字
				String name = Thread.currentThread().getName();
				int nowNum= 100 - pNum + 1;
				// 前门进
				if (name.equals("前门")) {
					if (nowNum < 10) {
						System.out.print("编号为:   ");
						
					} else if (nowNum < 100) {
						System.out.print("编号为:  ");
					} else {
						System.out.print("编号为: ");
					}
					System.out.print(nowNum + " 的员工,从前门入场!拿到的双色球彩票号码是:");
					printCP();
					pNum--;
					qNum++;
				}
				// 后门进
				if (name.equals("后门")) {
					if (nowNum < 10) {
						System.out.print("编号为:   ");
					} else if (nowNum < 100) {
						System.out.print("编号为:  ");
					} else {
						System.out.print("编号为: ");
					}
					System.out.print(nowNum + " 的员工,从后门入场!拿到的双色球彩票号码是:");
					printCP();
					pNum--;
					hNum++;
				}
				// 人数为0时,打印人数
				if (pNum == 0) {
					System.out.println("从前门入场的员工总共:" + qNum + "位员工");
					System.out.println("从后门入场的员工总共:" + hNum + "位员工");
				}
			}
			Thread.yield();
		}
	}
	// 打印彩票号的方法
	public void printCP() {
		ArrayList<Integer> list = new ArrayList<>();
		// 循环添加到数组中
		while(list.size() < 7) {
			// 随机数
			int num = (int)(Math.random() * (33 - 1 + 1) + 1);
			// 添加到数组中
			if (!list.contains(num)) {
				list.add(num);
			}
		}
		// 打印
		System.out.println(list);
	}
}

死锁

当两个或者两个以上的进程在执行工程中,由于竞争 CPU 资源
在相同时间或不同时间段内,循环等待该线程所要占用的资源,导致无限期的僵持

当多个线程需要相同的锁,但是按照不同的顺序加锁,就容易导致死锁的发生

模拟死锁
public class Demo {
	public static void main(String[] args) {
		DeadLockRunable deadLockRunable = new DeadLockRunable();
		Thread thread = new Thread(deadLockRunable);
		Thread thread1 = new Thread(deadLockRunable);
		thread.start();
		thread1.start();
	}
}
// A 锁
class LockA{
	// 私有构造方法
	private LockA() {}
	// 定义一个常量作为锁对象,不能修改,也不能创建
	public static final LockA LOCK_A = new LockA();
}
class LockB{
	private LockB() {}
	public static final LockB LOCK_B = new LockB();
}
// 线程
class DeadLockRunable implements Runnable{
	// 声明一个标记
	// 标记一次是先 A 后 B ,一次先 B 后 A 
	private boolean isTrue = true;
	@Override
	public void run() {
		// 死循环(增加死锁几率)
		while (true) {
			// 按标记
			if (isTrue) {
				// A 锁 -> B 锁
				synchronized (LockA.LOCK_A) {
					System.out.println("if...LOCK_A");
					synchronized (LockB.LOCK_B) {
						System.out.println("if...LOCK_B");
					}
				}
			} else {
				// B 锁 -> A 锁
				synchronized (LockB.LOCK_B) {
					System.out.println("else...LOCK_B");
					synchronized (LockA.LOCK_A) {
						System.out.println("else...LOCK_A");
					}
				}
			}
			// 修改标记
			isTrue = !isTrue;
		}
	}
}

Lock

Lock(JDK 1.5 锁)
定义 Lock 锁
Lock.lock();
try{
	写操作共享数据的代码
} finally{
	解锁
	lock.unlock();
}
public class Demo {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread thread = new Thread(ticket);
		Thread thread2 = new Thread(ticket);
		Thread thread3 = new Thread(ticket);
		thread.start();
		thread2.start();
		thread3.start();
	}
}
class Ticket implements Runnable {
	private static int tickets = 50;
	// 声明锁对象
	private ReentrantLock lock = new ReentrantLock();
	@Override
	public void run() {
		while(true) {
			// 加锁
			lock.lock();
			try {
				// 锁住操作共享数据的代码
				if (tickets > 0) {
					Thread.sleep(100);
					System.out.println(Thread.currentThread().getName() + ":" + tickets);
					tickets--;
				} else {
					break;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				// 解锁
				lock.unlock();
			}
			// 让出 CPU 资源
			Thread.yield();
		}
	}
}

线程中断

使用 stop 方法终止线程(不推荐,已过期)

使用 interrupt 方法终止线程

停止线程
只要线程停了,就是停止线程
使用 boolean 标记即可
测试 interrupt 中断线程
public class Demo {
	public static void main(String[] args){
		StopRunnable stopRunnable = new StopRunnable();
		Thread thread = new Thread(stopRunnable);
		thread.start();
		// 给 thread 线程有执行的时间
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread.interrupt();
		System.out.println("中断线程");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("主线程结束");
	}
}
class StopRunnable implements Runnable {
	private boolean isOver = false;
	public boolean isOver() {
		return isOver;
	}
	public void setOver(boolean isOver) {
		this.isOver = isOver;
	}
	@Override
	public void run() {
		// 利用死循环方式测试能不能停止线程
		while (!Thread.currentThread().isInterrupted()) {
			// 休眠一秒(死循环,让循环执行1s 结束)
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			Date date = new Date();
			DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
			String format = dateFormat.format(date);
			System.out.println(format);
		}
	}
}
// 使用标记法中断线程
public class Demo {
	public static void main(String[] args){
		StopRunnable stopRunnable = new StopRunnable();
		Thread thread = new Thread(stopRunnable);
		thread.start();
		// 给 thread 线程有执行的时间
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		stopRunnable.setOver(true);
		System.out.println("中断线程");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("主线程结束");
	}
}
class StopRunnable implements Runnable {
	private boolean isOver = false;
	public boolean isOver() {
		return isOver;
	}
	public void setOver(boolean isOver) {
		this.isOver = isOver;
	}
	@Override
	public void run() {
		// 利用死循环方式测试能不能停止线程
		while (!isOver) {
			// 休眠一秒(死循环,让循环执行1s 结束)
			long currentTimeMillis = System.currentTimeMillis();
			while (System.currentTimeMillis() - currentTimeMillis < 1000) {
				
			}
			Date date = new Date();
			DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
			String format = dateFormat.format(date);
			System.out.println(format);
		}
	}
}
/*
 * 测试结果:不能中断线程(结束子线程)
 * 这个方法的作用:
 * interrupt()这个方法,设置了 isInterrupted 的布尔值
 * 通过循环判断这个布尔值,来达到结束死循环从而结束线程的目的
 * 
 * 如果线程中有 wait() 等待或者 sleep() 休眠
 * 这时调用 interrupt()这个方法
 * 会抛出一个异常 InterruptedException 
 * 并且清除中断状态
 * 
 * 如果线程中没有 wait() 或者 sleep()
 * 这时调用 interrupt() 这个方法
 * 会设置中断状态(true/false的改变)
 */
/*
 * 测试中断状态
 * 注意:interrupt 方法尽量不要使用
 * 		如果要停止线程,直接使用标记法停止即可
 * 		只有遇到了等待状态时,可以使用该方法
 * 		强行清楚该等待状态
 * 
 * 
 * IllegalMonitorStateException
 * 对象监视器就是对象锁 synchronized
 */
public class Demo {
	public static void main(String[] args) {
		InterruptRunnable interruptRunnable = new InterruptRunnable();
		Thread thread1 = new Thread(interruptRunnable, "线程①");
		Thread thread2 = new Thread(interruptRunnable, "线程②");
		thread1.start();
		thread2.start();
		
		for (int i = 0; i < 50; i++) {
			if (i == 25) {
				// 调用中断方法,来清除状态
				thread1.interrupt();
				thread2.interrupt();
				break;
			}
			System.out.println(i + "*-*-*");
		}
		System.out.println("主线程结束");
	}
}
class InterruptRunnable implements Runnable {
	private boolean isOver = false;
	public boolean isOver() {
		return isOver;
	}

	public void setOver(boolean isOver) {
		this.isOver = isOver;
	}
	@Override
	public synchronized void run() {
		while (true) {
			try {
				/*
				 * 线程1进来带着锁,遇到 wait 方法
				 * 放弃 CPU 的执行权,锁还回去
				 * 线程2进来带着锁,遇到 wait 方法
				 * 放弃 CPU 的执行权,锁还回去
				 * 相当于两个线程都在这里等着
				 * 进入冷冻(中断)状态
				 * 强行解决冷冻(中断)状态
				 * 调用 interrupt 方法 清除该状态
				 */
				wait();// 线程等待,放弃了 CPU 的执行资源
				// wait 方法是 Object 类的
				// 注意: wait 方法必须用锁对象去调用
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			Date date = new Date();
			DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
			String format = dateFormat.format(date);
			System.out.println(Thread.currentThread().getName() + format);
		}
	}
}
public class Demo {
	public static void main(String[] args) {
		VolRunnable volRunnable = new VolRunnable();
		Thread thread = new Thread(volRunnable, "测试线程");
		thread.start();
		// 写个循环卡住主线程
		// 子线程修改的值,没有同步到主线程中
		while (!volRunnable.isOver()) {
		
		}
		System.out.println("主线程结束了");
	}
}
class VolRunnable implements Runnable {
	// volatile 关键字,可以把线程中的值,在修改之后,立即更新出去
	private volatile boolean isOver = false;
	private int num = 0;
	public boolean isOver() {
		return isOver;
	}

	public void setOver(boolean isOver) {
		this.isOver = isOver;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	@Override
	public void run() {
		Date date = new Date();
		DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
		String format = dateFormat.format(date);
		while (!isOver) {
			num++;
			// 把线程的执行时间加大
			// 防止上面还没执行到循环处,这里的标记值就已经改了
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(format);
			if (num > 4) {
				isOver = true;
			}
		}
	}
}
/*
 * 开启2个线程
 * 一个叫线程A
 * 一个叫线程B
 * 要求 
 * 先输入5遍  我是线程A
 * 再输出5遍  我是线程B
 */
public class Demo {
	public static void main(String[] args) {
		ABRunnable abRunnable = new ABRunnable();
		Thread thread = new Thread(abRunnable, "线程 A");
		Thread thread2 = new Thread(abRunnable, "线程 B");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		/*
		 * 线程 A 先进来
		 * 直接打印,唤醒(但是没有线程在等待)
		 * 线程 B 再进来,进入等待...无期限等待
		 * 解决方法
		 * B 后进来不进入等待,直接打印
		 * 
		 * 线程 B 先进来
		 * 进入等待,把锁还回去
		 * 线程 A 再进来,直接打印
		 * 唤醒线程 B, 线程 B 打印
		 */
		thread.start();
		thread2.start();
	}
}
class ABRunnable implements Runnable {
	// 利用标记,解决 A 先进来的情况
	/*
	 * 如果 A 先进来,直接打印,判断一下,如果是 A 则改变标记值
	 * 让 B 在进来的时候不去等待
	 */
	private boolean flag = false;
	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		// 保证一个线程没打完,另一个线程不能进去
		// 但是保证不了谁先进来
		while (true) {
			synchronized (this) {
				// 判断如果是 B 就让 B 进入等待状态( wait 状态)
				if (name.equals("线程 B") && flag == false) {
					// 调用 wait 方法,进入等待
					// 注意:使用锁对象去调用
					try {
						wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				// 打印名字5遍
				for (int i = 0; i < 5; i++) {
					System.out.println("我是" + name);
				}
				flag = !flag;
				// 等 A 打印完,需要唤醒等待中的线程
				// 注意: wait 和 notify 一般成对出现,都是使用锁对象调用
				// 随机唤醒等待线程中一个
				// notifyAll() 唤醒所有等待的线程
				this.notify();
			}
			Thread.yield();
		}
	}
}
/*
 * Person类 姓名 性别
 * 开启两个线程
 * 一个对Person对象进行赋值
 * 一个对Person对象进行打印
 * 要求
 * 一次打印 张三 男
 * 一次打印 张珊 女
 * 间隔输出
 * 
 * 1.先保证要操作的是同一个对象
 * 2.要保证数据安全需要使用锁,并且要使用同一把锁
 * 3.保证操作的逻辑顺序要对(先赋值,在打印)用 wait 和 notify
 */
public class Demo {
	public static void main(String[] args) {
		// 创建对象(只能使用同一个对象)
		Person person = new Person();
		
		// 创建线程
		SetRunnable setRunnable = new SetRunnable(person);
		Thread thread1 = new Thread(setRunnable);
		
		PrintRunnable printRunnable = new PrintRunnable(person);
		Thread thread2 = new Thread(printRunnable);
		
		thread1.start();
		thread2.start();
	}
}
// 赋值线程
class SetRunnable implements Runnable {
	// 利用成员变量,来操作同一个对象
	private Person person;
	// 定义一个标识通过改变标识来进行间隔赋值
	private boolean isTrue = true;
	// 利用构造方法来给成员变量赋值
	public SetRunnable() {
		super();
	}
	public SetRunnable(Person person) {
		super();
		this.person = person;
	}
	
	@Override
	public void run() {
		while (true) {
			// 多个线程操作共享数据
			// 需要添加锁
			// 为了保证两个同步锁的锁对象相同,使用传进来的对象 person
			/*
			 * 赋值线程需要赋值完毕,打印线程才能去打印
			 * 赋值时,打印线程等待,赋值完毕后,通知打印线程,打印线程才能打印
			 * 
			 * 同样,打印线程打印完毕,赋值线程才能赋值
			 * 打印时,赋值线程等待,打印完毕后,通知赋值线程,赋值线程才能赋值
			 * 
			 * wait 和 notify 使用一个标记来进行切换
			 * 这个标记赋值线程使用,也要给打印线程使用
			 * 注意:必须保证使用的是用一个标记,标记声明在 person 类中,即声明在共同对象中
			 * 
			 */
			synchronized (person) {
				// 判断 flag 为 true 时等待,不为 true 时赋值
				if (person.flag == true) {
					// 进行等待
					try {
						person.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// 间隔赋值
				if (isTrue) {
					person.name = "张三";
					person.gender = "男";
				} else {
					person.name = "zhangsan";
					person.gender = "nv";
				}
				// 改变标识符
				isTrue = !isTrue;
				// 修改标记
				person.flag = true;
				// 唤醒线程
				person.notify();
			}
		}
	}
}
// 打印线程
class PrintRunnable implements Runnable {
	// 利用成员变量,来操作同一个对象
	private Person person;
	public PrintRunnable() {
		super();
	}
	public PrintRunnable(Person person) {
		super();
		this.person = person;
	}

	@Override
	public void run() {
		while (true) {
			synchronized (person) {
				if (person.flag == false) {
					try {
						person.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// 打印
				System.out.println(person);
				// 修改标记
				person.flag = false;
				// 唤醒线程
				person.notify();
			}
		}
	}
}
class Person {
	public String name;
	public String gender;
	// 声明标记来切换打印和赋值
	public boolean flag = false;
	@Override
	public String toString() {
		return "姓名:" + name + "\n年龄:" + gender;
	}
	
}
对上个例题的代码优化
/*
 * Person1类 姓名 性别
 * 开启两个线程
 * 一个对Person1对象进行赋值
 * 一个对Person1对象进行打印
 * 要求
 * 一次打印 张三 男
 * 一次打印 张珊 女
 * 间隔输出
 * 
 * 1.先保证要操作的是同一个对象
 * 2.要保证数据安全需要使用锁,并且要使用同一把锁
 * 3.保证操作的逻辑顺序要对(先赋值,在打印)用 wait 和 notify
 */
public class Demo {
	public static void main(String[] args) {
		// 创建对象(只能使用同一个对象)
		Person1 person1 = new Person1();
		
		// 创建线程
		SetRunnable1 setRunnable1 = new SetRunnable1(person1);
		Thread thread1 = new Thread(setRunnable1);
		
		PrintRunnable1 printRunnable1 = new PrintRunnable1(person1);
		Thread thread2 = new Thread(printRunnable1);
		
		thread1.start();
		thread2.start();
	}
}
// 赋值线程
class SetRunnable1 implements Runnable {
	// 利用成员变量,来操作同一个对象
	private Person1 person1;
	// 定义一个标识通过改变标识来进行间隔赋值
	private boolean isTrue = true;
	// 利用构造方法来给成员变量赋值
	public SetRunnable1() {
		super();
	}
	public SetRunnable1(Person1 person1) {
		super();
		this.person1 = person1;
	}
	
	@Override
	public void run() {
		while (true) {
			/*
			 * 代码优化
			 * 把之前上锁的代码封装到类中
			 * 从同步代码块封装成同步方法
			 */
			if (isTrue) {
				person1.setPerson("张三", "男");
			} else {
				person1.setPerson("张珊", "女");
			}
			isTrue = !isTrue;
		}
	}
}
// 打印线程
class PrintRunnable1 implements Runnable {
	// 利用成员变量,来操作同一个对象
	private Person1 person1;
	public PrintRunnable1() {
		super();
	}
	public PrintRunnable1(Person1 person1) {
		super();
		this.person1 = person1;
	}

	@Override
	public void run() {
		while (true) {
			person1.print();
		}
	}
}
class Person1 {
	public String name;
	public String gender;
	// 声明标记来切换打印和赋值
	public boolean flag = false;
	
	// 把操作共享数据的部分写成一个同步的方法
	public synchronized void setPerson(String name, String gender) {
		// 等待和唤醒
		if (flag == true) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.name = name;
		this.gender = gender;
		// 更改标记
		flag = true;
		// 唤醒线程
		this.notify();
	}
	// 打印方法
	public synchronized void print() {
		if (flag == false) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(name + " " + gender);
		flag = false;
		this.notify();
	}
	
	@Override
	public String toString() {
		return "姓名:" + name + "\n年龄:" + gender;
	}
	
}
public class Demo {
	public static void main(String[] args) {
		SingleRunnable singleRunnable = new SingleRunnable();
		Thread thread1 = new Thread(singleRunnable);
		Thread thread2 = new Thread(singleRunnable);
		thread1.start();
		thread2.start();
	}
}
// 单例类 懒汉式
class Single {
	private static Single single = null;
	// 私有化构造方法
	private Single() {
		
	}
	// 获取单例对象的方法
	public static Single getInstance() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 提高效率
		if (single == null) {
			// 在这可以会有多个线程停留在这
			synchronized (Single.class) {
				// 造成数据不安全的原因
				// 同时有多个线程访问相同数据
				// 通过上锁去解决
				if (single == null) {
					single = new Single();
				}
			}
		}
		return single;
	}
}
class SingleRunnable implements Runnable {
	@Override
	public void run() {
		while (true) {
			// 获取单例对象
			Single single = Single.getInstance();
			// 查看是否为单例对象
			System.out.println(single.hashCode());
		}
	}
}

join 方法

public class Demo {
	public static void main(String[] args) {
		JRunnable jRunnable = new JRunnable();
		Thread thread = new Thread(jRunnable);
		thread.start();
		try {
			/*
			 * 告诉当前线程(主线程),线程要加入
			 * 等线程执行完毕时,当前线程再执行
			 */
			// 拿到所有 CPU 的执行权,停滞在这里
			// 等自己的方法执行完,再把 CPU 的执行权交回去
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for (int i = 0; i < 20; i++) {
			System.out.println(Thread.currentThread().getName() + i);
		}
		System.out.println("main is over~");
	}
}
class JRunnable implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);
		}
	}
}
例题:
/*
 * 保姆做家务
 * 保姆 来到洗衣间 衣服放入洗衣机
 * 洗衣机开始洗衣服 5次(1秒打印一次洗衣中…)
 * 然后 保姆来到客厅 扫地 12次
 * 洗衣机洗衣服 不影响保姆去客厅扫地
 * 然后 保姆去晒衣间 晒衣服 2次
 * (要求洗衣机洗完衣服 保姆也要扫完地才能去晒衣服)
 * 然后打印 家务处理完毕
 * 然后 保姆开始做 蹲 起 10次(交替 打印 蹲  起 各5次)
 * 蹲起完成后 最后打印 蹲起完成 很开心
 */
 // 睡眠时间,让线程"沉睡"1s
 public class Sleep {
	public static void time() {
		long currentTimeMillis = System.currentTimeMillis();
		while (System.currentTimeMillis() - currentTimeMillis < 1000) {
			
		}
	}
}
class CleanRunnable implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 12; i++) {
			Sleep.time();
			System.out.println("扫地中~");
		}
		System.out.println("打扫完成~");
	}
}
class SunRunnable implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 2; i++) {
			Sleep.time();
			System.out.println("晒衣服~");
		}
		System.out.println("衣服晒完了~");
	}
}
class WashRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("来到洗衣间,衣服放入洗衣机~");
		System.out.println("洗衣机开始洗衣服~");
		for (int i = 0; i < 5; i++) {
			Sleep.time();
			System.out.println("洗衣中~");
		}
		System.out.println("洗衣机结束洗衣服~");
	}
}
class DRunnable implements Runnable{
	private BM bm;
	public DRunnable() {
		super();
	}
	public DRunnable(BM bm) {
		super();
		this.bm = bm;
	}
	@Override
	public void run() {
		while (true) {
			// 跳出循环的条件
			if (bm.num == 10) {
				break;
			}
			bm.num++;
			synchronized (bm) {
				Sleep.time();
				if (bm.isOver == true) {
					try {
						bm.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("蹲~");
				bm.notify();
				bm.isOver = !bm.isOver;
			}
		}
	}
}
class QRunnable implements Runnable{
	private BM bm;
	public QRunnable() {
		super();
	}
	public QRunnable(BM bm) {
		super();
		this.bm = bm;
	}
	@Override
	public void run() {
		while (true) {
			// 跳出循环的条件
			if (bm.num == 10) {
				break;
			}
			bm.num++;
			synchronized (bm) {
				Sleep.time();
				if (bm.isOver == false) {
					try {
						bm.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("起~");
				bm.notify();
				bm.isOver = !bm.isOver;
			}
		}
	}
}
class BM implements Runnable{
	public boolean isOver = false;
	// 计数器
	public int num = 0;
	@Override
	public void run() {
		// 线程管理
		// 洗衣服
		WashRunnable washRunnable = new WashRunnable();
		Thread thread = new Thread(washRunnable);
		thread.start();
		// 衣服是洗衣机洗的,保姆可以继续去扫地
		CleanRunnable cleanRunnable = new CleanRunnable();
		Thread thread2 = new Thread(cleanRunnable);
		thread2.start();
		try {
			thread.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 等洗衣洗完,扫地扫完,去晒衣服
		SunRunnable sunRunnable = new SunRunnable();
		Thread thread3 = new Thread(sunRunnable);
		thread3.start();
		try {
			thread3.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("家务处理完毕~");
		// 蹲起
		// 把本类对象传进,使用 this
		DRunnable dqRunnable = new DRunnable(this);
		Thread thread4 = new Thread(dqRunnable);
		
		QRunnable qqRunnable = new QRunnable(this);
		Thread thread5 = new Thread(qqRunnable);
		thread4.start();
		thread5.start();
		try {
			thread4.join();
			thread5.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("蹲起完成 很开心~");
	}
}
public class Test {
	public static void main(String[] args) {
		BM bm = new BM();
		Thread thread = new Thread(bm);
		System.out.println("保姆开始做家务~");
		thread.start();
	}
}

接口回调

/*
 * 键盘输入
 * 1.红色打印  
 * 2.黑色打印  
 * 根据输入数字的不同进行打印(打印"接口回调"即可)
 * 
 * 定义一个接口 
 * 接口方法: 要求可以接收一个字符串做参数
 * 
 * 定义一个功能类
 * 要求: 接收一个接口对象作为参数 利用接口对象调用接口方法
 * 
 * 定义两个类 实现接口
 * 第1个类 普通打印
 * 第2个类 错误(err)打印
 *
 */
public class Demo {
	@SuppressWarnings("resource")
	public static void main(String[] args) {
		System.out.println("请输入 1.红色打印 2.黑色打印");
		while (true) {
			Scanner scanner = new Scanner(System.in);
			int num = scanner.nextInt();
			// 创建一个接口的空对象
			Inter inter = null;
			if (num == 1) {
				// 创建打印红色类的对象
				inter = new RedPrint();
			} else {
				// 创建打印黑色类的对象
				inter = new BlackPrint();
			}
			// 使用功能类(按照不同的对象打印)
			PrintClass.print(inter);
		}
	}
}
// 创建一个接口
interface Inter {
	// 打印字符串
	public abstract void print(String string);
}
// 创建两个类,实现接口
class RedPrint implements Inter {
	@Override
	public void print(String string) {
		// 使用红色打印
		System.err.println(string);
	}
}
class BlackPrint implements Inter {
	@Override
	public void print(String string) {
		// 使用黑色打印
		System.out.println(string);
	}
}
// 创建一个功能类,专门负责打印
// 不用管对象时谁,也不用管谁调用什么方法
// 只负责接受一个接口对象,调用接口方法
class PrintClass {
	// 打印根据不同的对象,调用不同的方法
	// 多态方法:增加方法扩展性
	// 使用接口当参数
	public static void print(Inter inter) {
		// 调用接口中的方法
		inter.print("接口回调");
	}
}
public interface ReadFileInter {
	public abstract void readFile(String string);
}
/*
 * 功能类
 */
public class Read {
	// 使用接口对象作为参数
	public static void printFile(ReadFileInter readFileInter) {
		// 开辟线程读取文件
		new Thread(new Runnable() {
			@Override
			public void run() {
				String str = "";
				try {
					FileReader fileReader = new FileReader("src/com/lanou3g/BM/BM.java");
					BufferedReader bufferedReader = new BufferedReader(fileReader);
					while ((str = bufferedReader.readLine()) != null) {
						// 读文件
						readFileInter.readFile(str);
					}
					bufferedReader.close();
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
}
/*
 * 利用接口实现,主线程处理逻辑
 * 子线程去读取文件,并打印
 */
public class Demo02 {
	 public static void main(String[] args) {
		 // 使用匿名对象方式传参
		 Read.printFile(new ReadFileInter() {
			// 匿名对象重写的方法
			@Override
			public void readFile(String string) {
				// 打印读取完的文件
				System.out.println(string);
			}
		});
	 }
}

http://blog.csdn.net/huzongnan/article/list

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值