欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java基础知识点总结--从细节再看Java(十一)--多线程开发

程序员文章站 2024-03-07 08:02:14
...

再说如何进行多线程开发之前,我们先来看看什么是线程,什么又是进程,两者有怎样的关系呢?

  • 程序(Program):

计算机指令的集合,以文件形式存储在磁盘上。即指一段静态代码,静态对象。

  • 进程(Process):

程序的一次动态执行过程, 占用特定的地址空间。在某种程度上进程是相互隔离、独立运行的程序。多任务操作系统将CPU时间动态地划分给每个进程,一次可同时执行多个进程,每个进程独立运行。

程序是静态的,进程是动态的。

  • 线程(Thread):

线程是进程中一个“单一的连续控制流程”  或一段执行路径。一个进程可拥有多个并行的(concurrent)线程。一个进程中的线程共享相同的内存单元或内存地址空间,一个进程中的线程可以访问相同的变量和对象,实现线程间的通信、数据交换、同步操作。

单线程:安全性高,效率低。

多线程:安全性低,效率高。

一、实现多线程

实现多线程,我们依赖一个类Thread,Thread类实现了Runnable接口,创建新执行线程有两种方法。

Thread中有定义了许多方法:

String getName() : 返回线程名;    

void SetName(String name) : 改变线程名称;

Thread(Runnable target):Thread的构造方法,获取Runnable的参数。

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

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

void setPriority(int newPriority) :更改线程的优先级。 优先级取值范围:1--10

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

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

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

  • 方法一

将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。

先看第一步,创建一个Thread的子类并重写run方法:

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

重写的run方法中描述的是要放在线程中执行的代码块。

接着我们再创建MyThread的对象并启动多个线程:

public class ThreadDemo {

	public static void main(String[] args) {
		//创建线程对象
		MyThread th = new MyThread();
		MyThread th2 = new MyThread();

		//为线程对象改名
		th.setName("A");
		th2.setName("B");

		//start方法启动线程
		th.start();
		th2.start();
	}

}
  • 方法二

创建线程的另一种方法是:声明实现 Runnable 接口的类,该类实现 run 方法。创建该类的实例,在创建 Thread 时将该实例作为一个参数来传递并启动。 

首先我们先创建一个实现Runnable接口的类并实现run方法:

class MyThread2 implements Runnable {

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++)
                        //这里使用Thread的静态方法currentThread()获取当前线程,再调用线程的getName()方法获取线程的名字
			System.out.println(Thread.currentThread().getName() + ":" + i);
	}

}

然后创建这个类的实例,并将其作为参数传入新创建的Thread对象中去,最后启动线程:

public class ThreadDemo {

	public static void main(String[] args) {
		// 创建线程实例,共享一个Runnable对象,如果有成员变量,其中的成员变量相同
		MyThread2 mt = new MyThread2();
		
		//创建 Thread 时将MyThread2的实例作为一个参数来传递
		Thread t = new Thread(mt);
		t.setName("A");
		Thread t2 = new Thread(mt);
		t2.setName("B");

		// 启动线程
		t.start();
		t2.start();
	}

 

二、多线程之间的同步方法

多线程虽然提高了程序的运行效率,同时也使得程序容易出现错误,安全性降低。当一个程序中有多个共享同一数据的线程时,这些线程并发访问这些共享的数据容易造成数据异常。

例如,出售火车票时,有多个窗口,我们把每一个窗口当作一个线程。如果我们不作任何处理,当票还剩最后一张时,线程A进来了,但此时它并没有立刻把票卖出去,而是去做别的事情。就在这个时候,线程B过来了,它一看票还剩最后一张也进来了,然后接着去做别的事情。这时线程A回来并把票卖出去了,此时票全卖完了,也就是0张票,之后再有线程来时发现票数<1就进不来了。但是,线程B已经获取了票的资源,当它处理完其他事情回来卖票时,票数又少了一张,即火车票成了-1张。而这就是线程并发访问时出现的问题。

怎么解决呢,我们需要一把锁,当一个线程进来获取资源之后,我们让它把锁锁上,这样当别的线程再来获取资源时,发现上锁了也就没法进来了,只好等上一个线程把锁释放之后才能进去获取资源。

那么在Java中我们要怎么实现呢,这是我们需要用一个关键字:synchronized。

synchronized:同步并修饰代码块或方法,被修饰的部分一旦被线程访问则直接锁死,防止其他线程访问。

同步代码块的实现方式:

synchronized(mutex){
    ...
}
//mutex需要被所有线程共享

同步方法的实现方式:

public synchronized void method(){
    ...
}
//非静态同步方法*用的锁对象是this

public static synchronized void method2(){
    ...
}
//静态方法优先于对象先加载,所以没有this
//静态同步方法*用的锁对象是当前类的字节码对象

我们来看一个简单的火车票出售同步程序

class TicketThread implements Runnable {

	//预出售的火车票总数
	int tickets = 100;
	//共享的锁对象
	Object obj = new Object();

	@Override
	public void run() {
		//窗口一直开放
		while (true) {
			//同步代码块
			synchronized (obj) {
				//当票数>0时操作
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + ",tickets=" + tickets--);
				}
			}
		}

	}

}

public class TicketThreadTest {
	public static void main(String[] args) {
		//创建自定义类的对象
		TicketThread tt = new TicketThread();

		//创建三个线程,代表三个窗口,传入自定义类的对象
		Thread t1 = new Thread(tt);
		t1.setName("窗口1");

		Thread t2 = new Thread(tt);
		t2.setName("窗口2");

		Thread t3 = new Thread(tt);
		t3.setName("窗口3");
		
		//启动线程
		t1.start();
		t2.start();
		t3.start();

	}
}

 

三、线程的生命周期

生命周期即一个对象的“生老病死”。线程的生命周期主要是一下几步:

Java基础知识点总结--从细节再看Java(十一)--多线程开发