java多线程-笔记

第一章:多线程的创建方式                                                    第二章:线程常用方法

第三章:多线程练习案例:模拟火车站多窗口售票                第四章:线程的安全问题

第五章:线程的同步机制                                                        第六章:线程同步练习案例:多用户存款

第七章:线程的通信                                                                第八章:线程的生命周期

第九章:经典案例:生产者/消费者

一:多线程的创建方式

1:将类声明为 Thread 的子类,该子类应重写 Thread 类的 run 方法

public class ThreadTest {

	public static void main(String[] args) {

		//创建一个线程并启动
		Thread thread = new MyThread();
		thread.start();

	}
}

class MyThread extends Thread {

	@Override
	public void run() {

		for (int i = 0; i < 100; i++) {
				System.out.println(Thread.currentThread().getName() + "----"
						+ i);
		}

	}
}

2:声明实现 Runnable 接口的类。该类然后实现 run 方法

public class ThreadTest {

	public static void main(String[] args) {

		//创建一个线程并启动
		Thread thread = new Thread(new MyThread());
		thread.start();

	}
}

class MyThread implements Runnable {

	@Override
	public void run() {

		for (int i = 0; i < 100; i++) {
				System.out.println(Thread.currentThread().getName() + "----"
						+ i);
		}

	}
}

二:线程常用方法

start():启动线程并执行相应的run()方法

run():线程要执行的代码放入run方法中

currentThread():静态方法  获取当前线程

getName():获取当前线程的名字
setName():设置线程的名称

yield():静态方法  执行此方法的线程释放当前cpu执行权

join():在A线程中调用B线程的join方法,表示:当执行的该方法,A线程停止执行,直至B线程执行完毕,A线程在接着执行join之后的代码

sleep(long time):静态方法  显示的让当前线程睡眠


setPriority():设置线程执行的优先级,默认是5,最大是10,最小是1
getPriority():获取线程执行的优先级

测试一:主线程和子线程分别打印1-100

public class ThreadTest {

	/**
	 * 使用到的方法:
	 * start():启动线程并执行相应的run()方法 
	 * currentThread():静态   获取当前线程
	 * getName():获取当前线程的名字 
	 * setName():设置线程的名称 
	 * setPriority():设置线程执行的优先级,默认是5,最大是10,最小是1 
	 * getPriority():获取线程执行的优先级
	 */
	public static void main(String[] args) {

		Thread.currentThread().setName("mainThread"); // 设置主线程的名称
		Thread.currentThread().getName(); // 获取主线程的名称
		Thread.currentThread().setPriority(10);// 设置主线程的执行优先级为10,优先级高低没有决定性,也就是优先级高的也不一定就是先执行或者先执行结束。

		Thread thread = new MyThread();// 创建一个子线程

		thread.setName("子线程");// 设置子线程名称
		thread.getName();// 获取子线程名称
		thread.setPriority(1);

		thread.start();// 启动子线程

		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "----" + i);
		}

	}
}

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

测试二:主线程和子线程分别打印1-100(注意观察打印效果)

public class ThreadTest {

	/**
	 *yield():执行此方法的线程释放当前cpu执行权
     *join():在A线程中调用B线程的join方法,表示:当执行的该方法,A线程停止执行,直至B线程执行完毕,A线程在接着执行join之后的代码
     *sleep(long time):显示的让当前线程睡眠
	 */
	public static void main(String[] args) throws Exception {

		Thread thread = new MyThread();// 创建一个子线程
		thread.start();// 启动子线程

		for (int i = 0; i < 100; i++) {
			// 在主线程里面分别使用yield()、join()、sleep()方法,观察结果
			if (i == 20){
				thread.join();//中断主线程,执行子线程,执行结果会很明显
				//Thread.currentThread().sleep(1000);//让主线程休眠1000毫秒
				//Thread.currentThread().yield(); //主线程释放这一次cpu执行权,但是下一次还是可能被主线程抢到,所以打印的结果并不是很明显
			}
			System.out.println(Thread.currentThread().getName() + "----" + i);
		}

	}
}

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

	}
}

三:多线程模拟火车站窗口售票

/**
 * 模拟火车站卖票:三个窗口一起出售100张车票
 */
public class ThreadTest {

	public static void main(String[] args) throws Exception {

		Window w = new Window();
		//创建三个线程
		Thread window1 = new Thread(w);
		Thread window2 = new Thread(w);
		Thread window3 = new Thread(w);
		//命名
		window1.setName("窗口1");;
		window2.setName("窗口2");
		window3.setName("窗口3");
        //开启线程售票
		window1.start();
		window2.start();
		window3.start();

	}
}

class Window implements Runnable {

	//定义一百张车票
	private static int ticket = 100;

	@Override
	public void run() {
		while (true) {
				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName() + "--售出第" + ticket + "张票");
					ticket--;
				} else {
					break;
				}
		}

	}
}

此程序存在线程安全问题:根据打印数据,发现重复票和错票

      

四:线程的安全问题

线程安全问题出现的原因:

当一个线程在操作共享资源(ticket)的过程中,突然失去了cpu的执行权,此时,cpu调度了另外一个线程也来操作这个共享资源,就会导致共享数据出现线程安全问题

结合售票代码和代码执行步骤图分析错票原因:

while (true) {
		if (ticket > 0) {
				
			try {
				Thread.currentThread().sleep(100); //线程睡眠,目的是让出现错票的概率增大
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
				
			System.out.println(Thread.currentThread().getName() + "--售出第"
					+ ticket + "张票");
			ticket--;
		} else {
			break;
		}
	}

当ticket剩下一张的时候,线程t1进来取票,判断条件ticket>0,满足,t1正准备做其他事情的时候,cpu突然放弃了t1,转过头去调度t2线程,t2线程这时候也进来取票,虽然t1取过了,但是并没有执行ticket--,所以t2来取票的时候,票数还是1,所以t2也通过了条件判断ticket>0,同理t3也进来取到票了,最后打印的时候就会出现0和-1的票。

五:线程的同步机制

如何来解决线程的安全问题?

>必须让一个线程操作共享数据完毕之后,其他线程才有机会进行共享数据的操作

java如何解决线程安全问题:线程的同步机制

        >方式一:同步代码块

          synchronized(同步监视器){

                //需要被同步的代码(即操作共享数据的代码)

          } 

          共享数据:多个线程共同操作的同一个变量

          同步监视器:俗称“锁”,由一个对象来充当,线程必须先拿到锁才能执行大括号中的代码,线程执行完大括号中的代码后             会释放锁,供其他线程使用。因为所有线程都共用一把“锁”,所以用来作为“锁”的对象必须是唯一的,否则实现不了同步

案例:同步代码块解决售票问题

class Window implements Runnable {

	// 定义一百张车票
	private static int ticket = 100;

	private static Object obj = new Object();// 可以new一个任何对象作为锁,因为是唯一,定义为静态

	@Override
	public void run() {
		while (true) {
			/**
			 * this表示使用自身对象作为锁,如果线程是实现Runnable接口可以使用 但是如果选择继承Thread对象,this需要慎用
			 * 具体原因在下面给出分析
			 */
			// synchronized (this) {
			synchronized (obj) {
				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName()
							+ "--售出第" + ticket + "张票");
					ticket--;
				} else {
					break;
				}
			}
		}

	}
}


/**
 * 模拟火车站卖票:三个窗口一起出售100张车票
 */
public class ThreadTest {

	public static void main(String[] args) throws Exception {

		/**
		 * 实现Runnable接口,创建多线程的时候就new了一个对象 所以可以使用this作为锁
		 */
		Window w = new Window();
		Thread window1 = new Thread(w);
		Thread window2 = new Thread(w);
		Thread window3 = new Thread(w);

		/**
		 * 继承Thread对象,创建多线程的时候需要new多个对象 如果使用this,那就是多把锁
		 */
		/*
		 * Window window1 = new Window(); Window window2 = new Window(); Window
		 * window3 = new Window();
		 */
		// 命名
		window1.setName("窗口1");
		window2.setName("窗口2");
		window3.setName("窗口3");
		// 开启线程售票
		window1.start();
		window2.start();
		window3.start();

	}
}

          >方式二:同步方法

           将操作共享资源的方法申明为synchronized,即此方法为同步方法,能够保证线程操作同步方法的时候,其他线程必须等            待该线程执行完此方法

           同步方法默认的锁:this(自身对象)

案例:同步方法解决售票问题

class Window implements Runnable {

	// 定义一百张车票
	private static int ticket = 100;

	@Override
	public void run() {
		while (true) {
			sail();
			if (ticket <= 0)
				break;
		}
	}

	public synchronized void sail() {
		if (ticket > 0) {
			System.out.println(Thread.currentThread().getName() + "--售出第"
					+ ticket + "张票");
			ticket--;
		}
	}
}

/**
 * 模拟火车站卖票:三个窗口一起出售100张车票
 */
public class ThreadTest {

	public static void main(String[] args) throws Exception {

		/**
		 * 因为同步方法默认用的锁是this,如果继承Thread实现多线程会导致同步失效
		 */
		Window w = new Window();
		Thread window1 = new Thread(w);
		Thread window2 = new Thread(w);
		Thread window3 = new Thread(w);

		// 命名
		window1.setName("窗口1");
		window2.setName("窗口2");
		window3.setName("窗口3");
		// 开启线程售票
		window1.start();
		window2.start();
		window3.start();

	}
}

六:线程同步练习案例:多用户存款

银行有一个账户。有三个储户分别向同一个账户存3000块钱,每次存1000,存三次,存完打印余额

//账户
class Account {

	//账户余额
	private int balance;

	//转账,因为balance是共享资源,如果这个方法不加同步会导致线程问题
	public synchronized void deposit(int money){
		balance += money;
		try {
			Thread.currentThread().sleep(100);//线程睡眠,为了使错误更明显
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"存款,账号余额为"+balance);
	}

}

//储户
class Customer extends Thread{
	
	//储户要转账的账户
	private Account account;
	
	//构造函数赋值
	public Customer(Account account) {
		super();
		this.account = account;
	}

	@Override
	public void run() {
		//向账户存3000块钱,每次存一千
		for (int i = 0; i < 3; i++) {
			account.deposit(1000);
		}
	}
	
}


public class Transfer {

	public static void main(String[] args) {
		Account account = new Account();
		
		//创建两个线程
		Customer customer1 = new Customer(account);
		Customer customer2 = new Customer(account);

		//设置两个线程的名称
		customer1.setName("用户1");
		customer2.setName("用户2");

		//开启线程
		customer1.start();
		customer2.start();
	}

}

运行结果:

问题扩展:实现两个储户交替存钱的操作

七:线程的通信

wait():令一个线程挂起并放弃当前cpu执行权、同步锁,使别的线程可以访问并修改共享资源,而当前线程排队等候再次访问资源。

notify():唤醒正在等待排队等待同步资源的线程中优先级最高的线程

notifyAll():唤醒正在排队等待资源的所有线程结束等待,

注意:这三个方法是 java.lang.Object 提供的,只有在synchronized方法或者synchronized代码块中才可以使用,否则会报java.lang.IllegalMonitorStateException异常

利用线程通信机制解决上一章的拓展问题:实现两个储户交替存钱

//账户
class Account {

	// 账户余额
	private int balance;

	// 转账,因为balance是共享资源,如果这个方法不加同步会导致线程问题
	public synchronized void deposit(int money){
		balance += money;
		
		notify();//当前线程进来之后,需要唤醒上一个进入等待的线程,否则会导致多个线程都在等待
		
		try {
			Thread.currentThread().sleep(100);// 线程睡眠,为了不加synchronized时候,使错误更明显
			
			System.out.println(Thread.currentThread().getName() + "存款,账号余额为"
					+ balance);

			wait();// 当前线程完成一次转账之后,进入等待,释放锁,让另一个线程进来执行转账
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

}

// 储户
class Customer extends Thread {

	// 储户要转账的账户
	private Account account;

	// 构造函数赋值
	public Customer(Account account) {
		super();
		this.account = account;
	}

	@Override
	public void run() {
		// 向账户存3000块钱,每次存一千
		for (int i = 0; i < 3; i++) {
			account.deposit(1000);
		}
	}

}

public class Transfer {

	public static void main(String[] args) {
		Account account = new Account();

		// 创建两个线程
		Customer customer1 = new Customer(account);
		Customer customer2 = new Customer(account);

		// 设置两个线程的名称
		customer1.setName("用户1");
		customer2.setName("用户2");

		// 开启线程
		customer1.start();
		customer2.start();
	}

}

运行结果:

八:线程的声明周期

新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread  t1=new Thread();

就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();

运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

自然终止:正常运行run()方法后终止

异常终止:调用stop()方法让一个线程终止运行

堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

正在等待:调用wait()方法。(调用motify()方法回到就绪状态)

被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

线程状态抓换图解:

注意sleep()和wait()方法的区别

sleep() 释放CPU的执行权,不释放锁

wait() 释放CPU的执行权,释放锁

九:多线程经典案例:生产者/消费者

生产者(producter)将产品交给店员(clerk),而消费者(customer)从店员这取走产品;店员一次只能持有固定数量的产品(比如20);如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店里有空位放置产品了再通知生产者生产;如果店里没有产品了,店员会告诉消费者等一下,如果店里有产品了再通知消费者来取走产品。

案例分析:

是否涉及多线程?: 涉及,生产者和消费者都是一个单独的线程

是否存在线程安全问题?:存在,因为生产者和消费者都需要操作共享资源“产品”

是否涉及线程通信?:涉及,产品多到一定数量后,生产者需要等待,暂时停止生产;产品空了,消费者需要等待,暂时停止消费;它们彼此也需要去唤醒对方进行生产或消费

//定义一个店员
class Clerk {

	private int product;// 产品数量

	public synchronized void addProduct() throws Exception {// 生产产品
		if (product >= 20) {
			System.out.println("产品已到上限,停止生产----------生产线程进入等待");
			wait();
		} else {
			product++;
			Thread.currentThread().sleep(1000); //放慢生产速度
			System.out.println("产品+1,数量为" + product);
			notify();
		}

	}

	public synchronized void subProduct() throws Exception {// 消费产品
		if (product <= 0) {
			System.out.println("产品已达下限,停止出售----------出售线程进入等待");
			wait();
		} else {
			product--;
			System.out.println("产品-1,数量为" + product);
			Thread.currentThread().sleep(1000); //放慢消费速度
			notify();
		}
	}
}

// 定义生产者
class Producer extends Thread {

	private Clerk clerk;

	// 构造器注入变量
	public Producer(Clerk clerk) {
		super();
		this.clerk = clerk;
	}

	@Override
	public void run() {
		// 生产者调用生产方法
		while (true) {
			try {
				clerk.addProduct();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

// 定义消费者
class Consumer extends Thread {

	private Clerk clerk;

	// 构造器注入变量
	public Consumer(Clerk clerk) {
		super();
		this.clerk = clerk;
	}

	@Override
	public void run() {
		// 消费者开始消费
		while (true) {
			try {
				clerk.subProduct();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}


//测试
public class ThreadTest {
	
	public static void main(String[] args) {
		//创建生产者和消费者
		Clerk clerk = new Clerk();
		
		Consumer consumer = new Consumer(clerk);
		Producer producer = new Producer(clerk);
		
		//开始生产者和消费者线程
		consumer.start();
		producer.start();
	}

}

猜你喜欢

转载自blog.csdn.net/qq_37936542/article/details/81944583
今日推荐