JavaSE——多线程

一、多线程概述

1、要认识进程得从程序开始,程序是对数据描述与操作的代码的集合;

2、进程是程序的一次动态执行过程,他对应了代码加载、执行至执行完毕的一个完整过程;

3、进程的特点是:

      (1)、进程是系统运行程序的基本单位;

      (2)、每一个进程都有自己独立的一块内存空间,一组系统资源;

      (3)、每一个进程的内部数据和状态都是完全独立的;

4、线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程

5、每个进程中至少包含一个主线程来作为这个程序运行的入口点

二、Java中实现多线程

1、Java在类和接口方面位多线程提供内置支持;

2、Java中通过Thread类将线程所必须的功能都封装了起来,常用方法如下:

      (1)、Thread():分配新的Thread对象;

      (2)、Thread( Runnable target ):分配新的Thread对象,target位run()方法被调用的对象;

      (3)、Thread( Runnable target,String name ):分配新的Thread对象,target位run方法被调用的对象,name为新线程的名字;

      (4)、void run():执行任务操作的方法;

      (5)、void start():使该线程开始执行,java虚拟机调用该线程的run()方法;

      (6)、void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);

      (7)、String getName():返回线程的名字;

              void setName():指定线程名字;

      (8)、int getPriority():返回线程的优先级;

      (9)、void setPrority(int newPriority):更改线程的优先级;

      (10)、static Thread currentThread():返回当前正在执行的线程对象的引用;

      (11)、boolean isAlive():测试线程是否处于活动状态;

      (12)、void join():等待线程终止;

      (13)、void interrupt():中断线程;

                boolean isInterrupted():判断当前线程是否是终止状态;

                boolean interrupted():清除中断状态;

      (14)、void yield():暂停当前正在执行的线程对象,并执行其他线程;

  3、主线程

      (1)、产生其他子线程的进程;

      (2)、通常它必须最后完成执行,因为它执行各种关闭操作;

package 多线程;
public class Test {
	public static void main(String[] args) {
		Thread t = Thread.currentThread();
		System.out.println("当前线程名是:"+t.getName());
		t.setName("测试线程");
		System.out.println("当前线程名是:"+t.getName());
		/**
		 * 当前线程名是:main
		 * 当前线程名是:测试线程
		 */
	}
}

4、使用线程的过程

      (1)、定义一个线程,同时指明这个线程所要执行的代码,即期望完成的功能;

      (2)、创建线程对象;

      (3)、启动线程;

      (4)、终止线程;

三、在Java中使用线程

1、在Java中创建线程类有两种方法,一种是集成Thread类,另一种是实现Runnable接口

2、继承Thread类创建线程

      使用此方法创建线程一般要经过以下步骤:

      (1)、继承Thread类;

      (2)、重写run方法,将所有需要线程执行的代码全部写在run方法中;

      (3)、创建线程对象;

      (4)、调用start方法启动线程;

package 多线程;

public class MyThread extends Thread{

	@Override
	public void run() {
		Thread.currentThread().setName("测试线程——");
		for (int i = 1; i < 11; i++) {
			System.out.println(Thread.currentThread().getName()+i);
		}
	}

	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		t1.start();
		t2.start();
	}

}

      上述两个线程对象调用start方法之后,都会执行run方法中的代码,相当于每个线程都有自己独立的执行路径,并行执行,互不影响,但由于CPU在一个时间点只能执行一个线程,因此多个线程是交替执行的;

3、实现Runnable接口创建线程

1、Runnable接口定义在java.lang包下,其中声明了一个抽象方法run,一个类可以通过实现Runnable方法,并实现该方法来完成成所有线程活动,已实现的run方法称为该线程对象的线程体

2、任何一个实现Runnable接口的对象都可以作为一个线程的目标对象

package 多线程;

public class MyRunnable implements Runnable{

	@Override
	public void run() {
		Thread.currentThread().setName("测试线程——");
		for (int i = 1; i < 11; i++) {
			System.out.println(Thread.currentThread().getName()+i);
		}
	}
	public static void main(String[] args) {
		MyRunnable myRunnable = new MyRunnable();
		Thread t = new Thread(myRunnable);
		t.start();
	}

}

      这种方式还可以实现多个线程之间的资源共享;

四、线程的状态

1、任何线程都具有五种状态:创建,就绪,运行,阻塞,死亡

2、创建状态:在程序中用构造方法创建了一个对象之后,该对象就处于创建状态;

      就绪状态:调用start方法启动线程,即进入就绪状态,此时线程将进入线程队列排队,等待CPU资源;

      运行状态:当就绪状态的线程获得CPU资源时,随即开始进入运行状态,执行run方法;

      阻塞状态:一个正在运行的线程因为某种原因不能继续运行时,进入阻塞状态,阻塞状态是一种"不可运行状态",处于这种状态的线程在得到一个特定的事件之后会转回运行状态,可是能线程进入阻塞状态的情形如下:

      (1)、由于线程优先级较低,不能获得CPU资源;

      (2)、sleep方法是线程休眠;

      (3)、wait方法是线程等待;

      (4)、yield方法是线程显式出让CPU控制权;

      (5)、线程由于等待一个文件或用户输入,I/O时间被阻塞;

      死亡状态:线程run方法执行完毕,则进入死亡状态,处于死亡状态的线程不具备继续运行的能力;

package 多线程;
public class MyRunnable implements Runnable{
	@Override
	public void run() {
		try {
			System.out.println("线程为运行状态!");
			Thread.currentThread().setName("测试线程——");
			//线程将在此处进入阻塞状态,休眠5秒后恢复运行
			Thread.currentThread().sleep(5000);
			for (int i = 1; i < 11; i++) {
				System.out.println(Thread.currentThread().getName()+i);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		MyRunnable myRunnable = new MyRunnable();
		Thread t = new Thread(myRunnable);
		System.out.println("线程为创建状态!");
		t.start();
		System.out.println("线程为就绪状态!");
	}
}

五、线程的调度

1、在单CPU计算机中,一个时刻只有一个线程运行,所谓多线程的并发运行,其实是从宏观上看,各线程轮流获得CPU资源的使用权;

2、线程优先级高的线程获得CPU资源的概率较大;

3、线程优先级用1~10表示,1表示优先级最低,默认值是5,这些优先级对应一个Thread类的公用静态常量;

public static fianl int NORM_PRIORITY=5;
public static fianl int MAX_PRIORITY=10;
public static fianl int MIN_PRIORITY=1;
package 多线程;
public class MyRunnable implements Runnable{
	@Override
	public void run() {
		for (int i = 1; i < 11; i++) {
			System.out.println(Thread.currentThread().getName()+i);
		}
	}
	public static void main(String[] args) {
		Thread t1 = new Thread(new MyRunnable(),"线程1——");
		Thread t2 = new Thread(new MyRunnable(),"线程2——");
		//设置线程的优先级
		t1.setPriority(1);
		t2.setPriority(10);
		//获取线程的优先级
		System.out.println("线程1的优先级是:"+t1.getPriority());
		System.out.println("线程2的优先级是:"+t2.getPriority());
		//启动线程
		t1.start();
		t2.start();
	}
}

六、线程的休眠

1、在程序中允许使用sleep方法是线程休眠,此时线程进入阻塞状态,休眠时间过后线程会再次进入就绪状态,进入线程队列排队,等待获取CPU资源,当获得CPU资源后,线程进入运行状态;

package 多线程;

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		try {
			for (int i = 1; i < 11; i++) {
				// 如果线程名位“线程1——”,则让其休眠1秒
				if (Thread.currentThread().getName().equals("线程1——")) {
					Thread.currentThread().sleep(1000);
				}
				System.out.println(Thread.currentThread().getName() + i);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new MyRunnable(), "线程1——");
		Thread t2 = new Thread(new MyRunnable(), "线程2——");
		// 设置线程的优先级
		t1.setPriority(1);
		t2.setPriority(10);
		// 获取线程的优先级
		System.out.println("线程1的优先级是:" + t1.getPriority());
		System.out.println("线程2的优先级是:" + t2.getPriority());
		// 启动线程
		t1.start();
		t2.start();
	}
}

七、线程的强制运行

1、join方法可以使调用该方法的线程优先执行,等调用该方法的线程执行完毕后,再执行其他线程;

package 多线程;

public class MyThread implements Runnable {

	@Override
	public void run() {
		try {
			for(int i=0;i<10;i++) {
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()+i);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
-----------------------------------------------------------------------------------
package 多线程;

public class MyThreadDemo {

	public static void main(String[] args) {
		Thread.currentThread().setName("主线程");
		Thread t = new Thread(new MyThread(),"子线程");
		t.start();
		for(int i=0;i<10;i++) {
			try {
				if(i==5) {
					//线程t调用了join方法,所以主线程会等线程t执行完毕之后再执行
						t.join();
				}
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()+i);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

八、线程的礼让

1、yield方法可暂停当前线程执行,允许其他具有相同优先级的线程获得运行机会,该线程仍处于就绪状态,不转为阻塞状态;

2、礼让只是提供一种可能性,但不能保证一定可以实现礼让,因为礼让的线程仍然处于就绪状态,还有可能被线程调度再次选中运行;

package 多线程;

public class MyThread implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName()+"正在运行!");
			if(i==3&&Thread.currentThread().getName().equals("线程A")) {
				System.out.print("线程礼让:");
				Thread.yield();
			}
		}
	}
}
------------------------------------------------------------------------------------
package 多线程;

public class MyThreadDemo {

	public static void main(String[] args) {
		MyThread my = new MyThread();
		Thread t1 = new Thread(my,"线程A");
		Thread t2 = new Thread(my,"线程B");
		t1.start();
		t2.start();
	}
}

      通过以上代码可得知,线程A在i=3的时候会礼让,但线程B不一定能获得运行机会;

九、多线程同步

1、多线程共享资源时引发的问题

      小明,小王和小强,三个人都在网上抢火车票,假设现在只有10张火车票,用多线程模拟这一过程,用sleep方法让线程睡眠500毫秒,来模拟网络延时;

package 多线程;

public class Site implements Runnable {
	/**
	 * count:总票数 
	 * num:现在售出第几张票
	 */
	private int count = 10;
	private int num = 0;

	@Override
	public void run() {
		while (true) {
			// 如果剩余总票数小于等于0,则跳出循环
			if (count <= 0) {
				break;
			}
			num++;
			count--;
			// 显示购票信息
			System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
			// 模拟网络延迟
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new Site(), "小明");
		Thread t2 = new Thread(new Site(), "小王");
		Thread t3 = new Thread(new Site(), "小强");
		t1.start();
		t2.start();
		t3.start();
		/**
		 * console:部分输出结果 
		 * ....
		 * 小明抢到了第4张票 
		 * 小强抢到了第4张票 
		 * 小王抢到了第4张票
		 * ...
		 */
	}
}

      从上述例子我们可以看到,多线程中在共享数据的时候,存在数据不安全的问题;

2、多线程同步的实现

      (1)、当多个线程对象共享同一数据资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用,这就是线程同步;

package 多线程;

public class Site implements Runnable {
	/**
	 * count:总票数 num:现在售出第几张票
	 */
	private static int count = 10;
	private static int num = 0;
	private static boolean flag = true;

	@Override
	public void run() {
		while (flag) {
			sale();
		}
	}

	public static synchronized void sale() {
		// 如果剩余总票数小于等于0,则跳出循环
		if (count <= 0) {
			flag = false;
			return;
		}
		num++;
		count--;
		// 显示购票信息
		System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
		// 模拟网络延迟
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new Site(), "小明");
		Thread t2 = new Thread(new Site(), "小王");
		Thread t3 = new Thread(new Site(), "小强");
		t1.start();
		t2.start();
		t3.start();
	}
}
-----------------------------------------------------------------------------------------
package 多线程;

public class Site implements Runnable {
	/**
	 * count:总票数 num:现在售出第几张票
	 */
	private int count = 10;
	private int num = 0;
	private boolean flag = true;

	@Override
	public void run() {
		while (flag) {
			sale();
		}
	}

	public synchronized void sale() {
		// 如果剩余总票数小于等于0,则跳出循环
		if (count <= 0) {
			flag = false;
			return;
		}
		num++;
		count--;
		// 显示购票信息
		System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
		// 模拟网络延迟
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Site site = new Site();
		Thread t1 = new Thread(site, "小明");
		Thread t2 = new Thread(site, "小王");
		Thread t3 = new Thread(site, "小强");
		t1.start();
		t2.start();
		t3.start();
	}
}

      注意以上两个例子的区别,被修饰符static修饰的变量和方法属于全局变量,可以控制该类所有的实例对象的线程;

      没有static修饰的变量和方法,属于实力属性和方法,只能限制当前对象的所有线程;

3、使用同步代码块实现线程同步

      将上面的例子稍微修改下:

package 多线程;

public class Site implements Runnable {
	/**
	 * count:总票数 num:现在售出第几张票
	 */
	private int count = 10;
	private int num = 0;
	private boolean flag = true;

	@Override
	public void run() {
		while (flag) {
			synchronized (this) {
				sale();
			}
		}
	}

	public void sale() {
		// 如果剩余总票数小于等于0,则跳出循环
		if (count <= 0) {
			flag = false;
			return;
		}
		num++;
		count--;
		// 显示购票信息
		System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
		// 模拟网络延迟
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Site site = new Site();
		Thread t1 = new Thread(site, "小明");
		Thread t2 = new Thread(site, "小王");
		Thread t3 = new Thread(site, "小强");
		t1.start();
		t2.start();
		t3.start();
	}
}

      使用同步代码块也可以实现对多线程的同步,不过同步代码块锁的是当前的对象;

      得出结论:当synchonized锁的方法是实例方法或锁的是代码块时,那么它就是一个对象锁,只能对当前对象进行控制;

                        当synchonized锁的方法是静态方法时,那么他就是一个类锁,可以控制该类下所有对象; 

package 多线程;

public class Run implements Runnable {
	//赛道总长度
	private static int meters = 400;
	private boolean flag = true;
	private static Run run = new Run();

	@Override
	public void run() {
		while (flag) {
			synchronized (run) {
				//总长度跑完即结束
				if (meters == 0) {
					flag = false;
					break;
				}
				begin();
			}
		}
	}

	public void begin() {
		System.out.println(Thread.currentThread().getName() + "选手拿到了接力棒!");
		//每人跑100米
		for (int i = 0; i < 100; i += 10) {
			System.out.println(Thread.currentThread().getName() + "选手跑了" + (i + 10) + "米");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		meters -= 100;
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(run, "1号");
		Thread t2 = new Thread(run, "2号");
		Thread t3 = new Thread(run, "3号");
		t1.start();
		t2.start();
		t3.start();
	}
}
-----------------------------------------------------------------------------------------
package 多线程;

public class Run2 implements Runnable {
	// 赛道总长度
	private static int meters = 400;
	private static boolean flag = true;

	@Override
	public void run() {
		while (flag) {
			begin();
		}
	}

	public static synchronized void begin() {
		// 总长度跑完即结束
		if (meters == 0) {
			flag = false;
			return;
		}
		System.out.println(Thread.currentThread().getName() + "选手拿到了接力棒!");
		// 每人跑100米
		for (int i = 0; i < 100; i += 10) {
			System.out.println(Thread.currentThread().getName() + "选手跑了" + (i + 10) + "米");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		meters -= 100;
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new Run2(), "1号");
		Thread t2 = new Thread(new Run2(), "2号");
		Thread t3 = new Thread(new Run2(), "3号");
		t1.start();
		t2.start();
		t3.start();
	}
}

      注意区分上述Run类和Run2类中的代码,一个是对象锁,一个是类锁,但实现的效果是相同的;

猜你喜欢

转载自blog.csdn.net/dyz4001/article/details/81607659