java复习第十八天 线程和多线程的语法,用法,原理,

多线程面试题

多线程有几种实现方案,分别是哪几种?
方式一:定义一个子类继承Thread,然后重写run方法,将需要给子线程执行的代码放在run方法里面;然后创建子类对象,通过子类对象调用start方法
方式二:定义一个子类实现Runnable接口,实现run方法,将需要给子线程执行的代码放在run方法里面;然后在创建线程时,
将子类对象当做参数传递,然后调用线程的start方法
同步有几种方式,分别是什么?
锁对象:可以是任意对象,所有的线程需要使用同一个锁对象

方式一:使用同步代码块
方式二:使用同步方法
方式三:使用JDK1.5新特性的Lock锁对象
启动一个线程是run()还是start()?它们的区别?
run:就是一个普通的方法
start:会和系统进行交互,启动线程,启动线程之后会默认去调用run方法
sleep()和wait()方法的区别
sleep:不能被唤醒,但是可以被中断;调用sleep时,锁对象不会释放
wait:可以被中断,可以被唤醒;wait只能在同步代码中使用锁对象来调用;调用wait时,锁对象会释放
线程的生命周期图
看图解
生产者和消费者:wait,notify,notifyAll

1.多线程的创建和原理

/*
 * 一个线程一个栈,堆和方法区是共享的
 * 
 * 	为什么要重写run()方法:因为子线程启动之后会默认去调用run方法,所以需要被子线程执行的代码就放在run方法中
	启动线程使用的是那个方法:start
	线程能不能多次启动:不行
	run()和start()方法的区别:
		t.run();//这样调用就相当于一个对象调用一个方法,这个run方法还是在主线程中执行的
		t.start();//首先会创建一个子线程,然后子线程默认就去调用线程中的run方法,所以run方法是在子线程中执行
 */
public class ThreadDemo {
	public static void main(String[] args) {
		//创建子线程对象
		MyThread t = new MyThread();
		//t.run();//这样调用就相当于一个对象调用一个方法,这个run方法还是在主线程中执行的
		t.start();//首先会创建一个子线程,然后子线程默认就去调用线程中的run方法,所以run方法是在子线程中执行
		System.out.println("over");
	}
}

// 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法
class MyThread extends Thread {
	@Override
	public void run() {
		// 交个子线程做的事情
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
		}
	}
}

2.多线程

/*
 * 如何获取线程的名称:public final String getName()返回该线程的名称
 * 如何设置线程的名称:public final void setName(String name)改变线程名称,使之与参数 name 相同
 * 如何获取主线程的名称:public static Thread currentThread()返回对当前正在执行的线程对象的引用
 */
public class ThreadDemo2 {
	public static void main(String[] args) {
		//创建子线程并启动
		MyThread2 t = new MyThread2();
		t.start();
		//如何获取线程的名称:public final String getName()返回该线程的名称
		System.out.println(t.getName());//Thread-0
		t.setName("子线程1");
		
		MyThread2 t2 = new MyThread2();
		t2.start();
		System.out.println(t2.getName());//Thread-1
		t2.setName("子线程2");
		
		MyThread2 t3 = new MyThread2("子线程3");
		t3.start();
		System.out.println(t3.getName());//子线程3
		
		//如何获取主线程的名称:public static Thread currentThread()返回对当前正在执行的线程对象的引用
		String name = Thread.currentThread().getName();
		System.out.println(name);//main
		Thread.currentThread().setName("主线程");
		//获取主线程的名称
		System.out.println(Thread.currentThread().getName()+"---over");
	}
}
//创建一个子类继承Thread,然后重写run方法
class MyThread2 extends Thread{
	public MyThread2(){
		
	}
	public MyThread2(String name){
		super(name);
	}
	@Override
	public void run() {
		for(int i=0; i<10; i++){
			System.out.println(getName()+"---"+i);
		}
	}
}

3.sleep的用法

//sleep用法
public class ThreadDemo3 {
	public static void main(String[] args) {
		MyThread3 t = new MyThread3();
		t.start();
		//public static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
		try {
			Thread.sleep(1000);//主线程睡眠
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("over");
	}
}
class MyThread3 extends Thread{
	@Override
	public void run() {
		for(int i=0; i<10; i++){
			System.out.println(getName()+"---"+i);
		}
	}
}

4.join

//join用法
public class ThreadDemo4 {
	public static void main(String[] args) {
		// 面试题:让t先执行,然后t2执行,然后t3执行
		MyThread4 t = new MyThread4();
		t.start();

		// 让主线程等待t线程之后完毕之后再运行
		try {
			//public final void join()如果join()方法在一个线程实例上调用,当前运行着的线程将阻塞直到这个线程实例完成了执行之后再执行
			t.join();// 让当前线(主线程)程等待t线程执行完毕之后再执行
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		MyThread4 t2 = new MyThread4();
		t2.start();

		// 让主线程等待t线程之后完毕之后再运行
		try {
			t2.join();// 让当前线(主线程)程等待t2线程执行完毕之后再执行
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		MyThread4 t3 = new MyThread4();
		t3.start();

	}
}

class MyThread4 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName() + "---" + i);
		}
	}
}

5.stop用法和替代方案

//stop用法和替代方案
public class ThreadDemo5 {
	public static void main(String[] args) {
		MyThread5 t = new MyThread5();
		t.start();
		t.setRunning(false);
		System.out.println("over");
	}
}

class MyThread5 extends Thread {
	private boolean isRunning = true;

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			// 当i等于3的时候,终止当前的子线程
			if (i == 3) {
				// stop();//已过时,应该由一个变量来决定是否终止线程
				isRunning = false;
			}
			if (isRunning) {
				System.out.println(getName() + "---" + i);
			} else {
				return;
			}
		}
		System.out.println("哈哈哈");
	}

	public void setRunning(boolean b) {
		isRunning = b;
	}
}

6.interrupt用法:必须线程是阻塞状态才能看到效果

//interrupt用法:必须线程是阻塞状态才能看到效果
public class ThreadDemo6 {
	public static void main(String[] args) {
		MyThread6 t = new MyThread6(Thread.currentThread());//把主线程对象传递给子线程
		t.start();
		
		try {
			Thread.sleep(100);//让主线程休眠100毫秒,确保子线程是睡眠状态
		} catch (InterruptedException e) {
			//e.printStackTrace();
			System.out.println(Thread.currentThread().getName()+"阻塞状态被中断");
		}
		t.interrupt();//中断子线程
		 	
		
		try {
			//Thread.sleep(2000);//主线程阻塞了1秒钟
			t.join();//让主线程等待t线程之后完毕之后再执行
		} catch (InterruptedException e) {
			//e.printStackTrace();
			System.out.println(Thread.currentThread().getName()+"阻塞状态被中断");
		}
		
		System.out.println("over");
	}
}

class MyThread6 extends Thread {
	private Thread mainThread;//主线程

	public MyThread6(Thread mainThread) {
		this.mainThread = mainThread;
	}

	@Override
	public void run() {
		mainThread.interrupt();//立马打破主线程的阻塞状态
		try {
			sleep(2000);//子线程睡眠2秒钟
		} catch (InterruptedException e) {
			//e.printStackTrace();
			System.out.println(Thread.currentThread().getName()+"阻塞状态被中断");
		}
		for (int i = 0; i < 10; i++) {
			System.out.println(getName() + "---" + i);
		}
	}
}

7.创建方式2

//创建并启动线程方式二
public class ThreadDemo7 {
	public static void main(String[] args) {
		// 然后可以分配该类的实例
		MyRunnable task = new MyRunnable();// 一个任务可以被多个线程执行

		// 在创建 Thread 时作为一个参数来传递并启动
		Thread t = new Thread(task, "子线程1");
		t.start();// 子线程启动之后会去调用task的run方法,那么run方法就在子线程中执行了
		Thread t2 = new Thread(task, "子线程2");
		t2.start();
		Thread t3 = new Thread(task, "子线程3");
		t3.start();
		try {
			t.join();
			t2.join();
			t3.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("over");
	}
}

// 创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread
// 时作为一个参数来传递并启动。
class MyRunnable implements Runnable {

	@Override
	public void run() {
		// 存放给子线程执行的代码
		for (int i = 0; i < 10; i++) {
			// 获取当前线程的名字
			System.out.println(Thread.currentThread().getName() + "---" + i);
		}
	}

}

8.创建方式3

public class ThreadDemo {
	public static void main(String[] args) {
		//传统方式一:继承Thread,重写run方法,创建子类实例调用start方法
		new MyThread().start();//Thread-0:继承Thread方式创建子线程
		//匿名内部类方式一:匿名内部类重写Thread的run方法方式创建子线程
		new Thread(){
			public void run() {
				System.out.println(getName()+":匿名内部类重写Thread的run方法方式创建子线程");
			};
		}.start();//Thread-1:匿名内部类重写Thread的run方法方式创建子线程
		
		//传统方式二:实现Runnable接口,实现run方法
		new Thread(new MyRunnable()).start();//Thread-2:实现Runnable接口方式创建子线程
		//匿名内部类方类方式二:匿名内部类实现Runnable接口的方式创建子线程
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()+":匿名内部类实现Runnable接口的方式创建子线程");
			}
		}).start();//Thread-3:匿名内部类实现Runnable接口的方式创建子线程
		
		System.out.println(Thread.currentThread().getName()+":over");//main:over
	}
}
//传统方式一:继承Thread,重写run方法,创建子类实例调用start方法
class MyThread extends Thread{
	@Override
	public void run() {
		System.out.println(getName()+":继承Thread方式创建子线程");
	}
}
//传统方式二:实现Runnable接口,实现run方法
class MyRunnable implements Runnable{

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+":实现Runnable接口方式创建子线程");
	}
	
}

9.生产者和消费者模式

public class Apple {
	private String name;
	private float price;
	public Apple() {
		super();
	}
	public Apple(String name, float price) {
		super();
		this.name = name;
		this.price = price;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public float getPrice() {
		return price;
	}
	public void setPrice(float price) {
		this.price = price;
	}
	@Override
	public String toString() {
		return "Apple [name=" + name + ", price=" + price + "]";
	}
}

消费者

import java.util.concurrent.LinkedBlockingQueue;

//消费者
public class CustomerRunnable implements Runnable {
	private LinkedBlockingQueue<Apple> queue;

	public CustomerRunnable(LinkedBlockingQueue<Apple> queue) {
		this.queue = queue;
	}

	@Override
	public void run() {
		while (true) {
			synchronized (queue) {
				// 如果队列中没有产品了,那么就wait当前的消费线程
				while (queue.size() == 0) {
					try {
						queue.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				try {
					// 从queue中取出产品消费
					System.out.println(Thread.currentThread().getName());
					//Apple apple = queue.poll();//没有值就返回null
					Apple apple = queue.take();//底层通过它自身的锁对象调用了await方法,那么这行代码就阻塞在这里了,那么queue锁对象就没有被释放
					System.out.println(Thread.currentThread().getName() + "消费了:" + apple.getName());
				} catch (Exception e) {
					e.printStackTrace();
				}
				// 唤醒生产者去生产
				queue.notifyAll();
			}
			// 模拟现实生活中的延迟
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

生产者、

import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

//生产者
public class ProductRunnable implements Runnable {
	private LinkedBlockingQueue<Apple> queue;

	public ProductRunnable(LinkedBlockingQueue<Apple> queue) {
		this.queue = queue;
	}

	@Override
	public void run() {
		Random r = new Random();
		while (true) {
			synchronized (queue) {
				// 只要queue里面存放的产品大于或者等于10个了,生产者线程就wait
				while (queue.size() >= 10) {
					try {
						queue.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				// 开始生产
				Apple apple = new Apple("苹果" + r.nextInt(1000), r.nextInt(100) + 1);
				try {
					// 把苹果放进队列中
					queue.put(apple);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "生产了:" + apple.getName());
				// 唤醒消费者去消费
				queue.notifyAll();
			}
			//模拟现实生活中的延迟
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

入口

import java.util.concurrent.LinkedBlockingQueue;

/*
 * wait,notify,notifyAll为什么定义在Object类中呢?
 * 	因为锁对象可以是任意对象,又因为wait,notify,notifyAll必须通过锁对象调用;
 * 	那么也就是说wait,notify,notifyAll是可以被任意对象调用方法,那么可以被任意对象调用的方法应该定义在Object类中
 * 
 *  wait,notify,notifyAll它们必须在同步代码中,使用相同的锁对象进行调用
 *  
 *  wait被调用的时候,当前的锁对象会释放
 */
public class ThreadTest {
	public static void main(String[] args) {
		// 创建一个阻塞队列LinkedBlockingQueue
		LinkedBlockingQueue<Apple> queue = new LinkedBlockingQueue<Apple>();

		ProductRunnable productRunnable = new ProductRunnable(queue);
		new Thread(productRunnable, "生产者1").start();
		new Thread(productRunnable, "生产者2").start();
		new Thread(productRunnable, "生产者3").start();
		new Thread(productRunnable, "生产者4").start();
		new Thread(productRunnable, "生产者5").start();

		CustomerRunnable customerRunnable = new CustomerRunnable(queue);
		new Thread(customerRunnable, "消费者1").start();
		new Thread(customerRunnable, "消费者2").start();
		new Thread(customerRunnable, "消费者3").start();
		new Thread(customerRunnable, "消费者4").start();
		new Thread(customerRunnable, "消费者5").start();
	}
}

10 火车站售票之多人同时购票的实现

/*
 * 需求:春节某火车站正在售票,假设还剩100张票,而它有3个售票窗口售票,请设计一个程序模拟该火车站售票。 
		在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟,每次卖票延迟100毫秒
	两种方式实现
		继承Thread类
		实现Runnable接口

 */
public class SaleTicketDemo {
	public static void main(String[] args) {
		// 继承Thread类方式:
//		 new SaleTicketThread("窗口1").start();
//		 new SaleTicketThread("窗口2").start();
//		 new SaleTicketThread("窗口3").start();

		// 实现Runnable接口方式:
		 TicketRunnable ticketRunnable = new TicketRunnable();
		 new Thread(ticketRunnable, "窗口1").start();
		 new Thread(ticketRunnable, "窗口2").start();
		 new Thread(ticketRunnable, "窗口3").start();
	}
}

票数的实现

public class SaleTicketThread extends Thread {
	// 总共100张票
	private static int ticket = 100;

	public SaleTicketThread(String name) {
		super(name);
	}

	@Override
	public synchronized void run() {//不能使用同步方法,因为同步方法的锁对象是this;而我们这个SaleTicketThread创建了三个对象,就有三个this
		while (true) {
			// synchronized (Object.class) {
			if (ticket > 0) {
				System.out.println(getName() + "出售了第" + ticket + "张票");
				ticket--;// 票需要-1
			} else {
				return;
			}
			// }
			// 模拟现实生活中卖票的延迟
			try {
				sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
模拟售票
mport java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
 * 如何判断一个程序有没有线程安全问题?
	是否存在多线程环境
	是否存在共享数据
	是否存在多条语句操作共享数据
怎么解决线程同步安全问题呢?
	把存在线程安全问题的代码锁起来,让任意时刻只有一个线程能访问
	
解决方式一:同步代码块
	synchronized(锁对象){
		//相当于上锁
		存在线程安全问题的代码
		//相当于解锁
	}
解决方式二:同步方法
	将整个方法都锁起来,在方法的修饰符位置上加synchronized

锁对象:可以是任意对象,多个线程使用的是必须同一个锁对象	
同步代码块:可以是任意对象,多个线程使用的是同一个锁对象
非静态同步方法:锁对象是this
静态同步方法:锁对象是当前字节码文件对象

解决方式三:使用Lock锁对象
	Lock lock = new ReentrantLock();
	lock.lock();//上锁
	lock.unlock();//释放锁

HashTable:用的就是同步方法
ConcurrentHashMap:用的就是同步代码块
 */
public class TicketRunnable implements Runnable {
	// 总共100张票
	private int ticket = 100;
	private Lock lock = new ReentrantLock();
	
	@Override
	public void run() {
		while (true) {
			// synchronized (Object.class) {
			lock.lock();//上锁
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "出售了第" + ticket + "张票");
				ticket--;// 票需要-1
			} else {
				return;
			}
			lock.unlock();//释放锁
			// }
			// 模拟现实生活中卖票的延迟
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

电脑执行流程



线程生命周期图解


生产者和消费者设计模式图解

多线程的引入



售票解决方案



猜你喜欢

转载自blog.csdn.net/a331685690/article/details/80070432