Java多线程技术介绍

使用软件eclipse 2019-2,jdk11

首先,了解两个概念:进程与线程

进程就好比是在一块儿内存里运行的程序,这个程序拥有独立的栈和堆,而线程就是进程里的一条条执行路径,他们拥有各自的栈,共享进程里的堆。一个进程至少要包含一个线程。

在Java中,使用的线程调度方法为抢占式调度:就是字面意思,所有的线程去抢执行的机会,优先级高的线程抢到执行机会的概率就大,反之就小。

其次,还要知道并发和并行的概念:并发,就是两个及以上的事件(线程)在同一时间段内执行,并行则是这些事件(线程)在同一时间点执行。举个简单例子就是,你在一周内完成了一次长跑,做完了一个项目,这是并发;一边吃饭一边看电视这就属于并行。

Java中有三种实现多线程的方式。

1.继承Thread类,并重写其run方法,这个run方法里放需要执行的代码,再通过创建Thread子类对象,调用其start方法执行

例:
public class ThreadTest {
	public static void main(String[] args) throws InterruptedException {
		//创建继承了Thread的Person的对像
		Person p = new Person();
		//调用start方法
		p.start();
		for(int i=0;i<10;i++) {
			System.out.println("hahaha"+i);
		}
	}
	/**
	 * Person类继承Thread
	 * @author 
	 *
	 */
	static class Person extends Thread{
		@Override
		/**
		 * 重写run方法,加入执行语句
		 */
		public void run() {
			for(int i=0;i<10;i++) {
				System.out.println("heihei"+i);
			}
		}
	}
}
这样就创建好了一个线程,可以创建多个。new新的对象调用start即可

2.实现Runnable接口,重写run方法。操作与第一个方法类似,编写好实现Runnable接口的类并实现run方法后,需要创建该类的对象,再创建Thread对象传入这个实现Runnable接口类的对象,再用Thread对象调用start

public class ThreadTest {
	public static void main(String[] args) throws InterruptedException {
		//创建实现了Runnable接口的Person的对像
		Person p = new Person();
		Thread t = new Thread(p);
		//调用start方法
		t.start();
		for(int i=0;i<10;i++) {
			System.out.println("hahaha"+i);
		}
	}
	/**
	 * Person类实现Runnable接口
	 * @author 
	 *
	 */
	static class Person implements Runnable{
		@Override
		/**
		 * 重写run方法,加入执行语句
		 */
		public void run() {
			for(int i=0;i<10;i++) {
				System.out.println("heihei"+i);
			}
		}
	}
}
因为Java不允许继承多个父类,但是可以实现多个接口,所以这种方法比第一种的操作空间要大。通过实现Runnable接口这种方式创建的是执行任务并非之间创建了一个线程,任务与线程是分离的。降低了耦合性

3.实现Callable接口,具体步骤为:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadTest {
	/**
	 * 1.编写类实现Callable接口,实现call方法
	 * 2.创建FutureTask对象,传入第一步编写的类的对象
	 * 3.通过Thread对象调用start启动
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		//创建实现了Callable接口的Person的对像
		Person p = new Person();
		FutureTask<String> ft = new FutureTask<>(p);
		Thread t = new Thread(ft);
		//调用start方法
		t.start();
		for(int i=0;i<10;i++) {
			System.out.println("hahaha"+i);
		}
	}
	/**
	 * Person类实现Callable接口
	 * @author 
	 *
	 */
	static class Person implements Callable<String>{
		@Override
		/**
		 * 重写call方法,加入执行语句
		 */
		public String call() {
			for(int i=0;i<10;i++) {
				System.out.println("heihei"+i);
			}
			return "执行完毕";
		}
	}
}
此方法创建的线程带有返回值。在主线程调用了此方法创建的线程对象的get方法后,会等待此线程结束后才会继续执行,在get方法里传入时间以及时间单位(TimeUnit类里的常量),就可以在指定时间未获取到返回值,继续运行

特性总结

线程分为用户线程与守护线程,用户线程就是直接创建的线程(通过Thread对象调用setDaemon()方法传入true可设置为守护线程),守护线程则是用来守护用户线程的,在所有的用户线程结束后,守护线程自动结束。

线程拥有六种状态,分别是new:线程刚刚创建,但未执行任务;runnable:正在执行任务;blocked:等待抢占机会;waiting:无限制的等待,直到被其他线程唤醒;time Waiting:指定时间等待,一段时间没被唤醒,自动唤醒;trminated:终止;

进程在执行过程中,其内部的线程难免会发生阻塞,线程阻塞就指的是比较消耗时间的操作。有时会因为一些特殊情况发生死锁(有两个线程,同时分别占用了两个对象,都在等对方释放使用权),所以在进行多线程操作时,在可能产生锁的方法里,尽量不要调用另外的方法。

线程之间在使用变量时,会导致数据不安全的问题,首先了解两个概念:同步与异步,同步是指排队执行,效率低但是安全;异步,就是同时执行效率高,不安全;

就好比街上有个买菜的,你上街看到了想买菜,挑好菜后掏钱的时间你看好的菜被别人买走了这就是异步,同步则是一个人买完下一个买。放在程序里异步就是,某个线程在拿到这个变量后还没来得及使用,被另一个线程使用并更新了值,此时前面的线程以为变量没有更改,还会继续执行,就导致bug产生,同步则是,一个线程在使用这个变量时其他线程需要等待前者使用完。

要实现同步操作可以通过synchoroized修饰符,把需要加锁的代码块用其修饰即可

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class ThreadTest {
	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		//创建执行任务的对象
		Team team = new Team();
		//创建三条线程
		Thread t1 = new Thread(team);
		Thread t2 = new Thread(team);
		Thread t3 = new Thread(team);
		//调用start方法
		t1.start();
		t2.start();
		t3.start();
	}
	/**
	 * 此类描述一队人,走出一个房间,房间内剩余多少人
	 * @author
	 *
	 */
	static class Team implements Runnable{
		//队伍初始人数
		private int teamNum = 20;
		//用来锁定使用权限的锁,可以是任意的对象
		private Object obj = "lock";
		public Team() {
			// TODO Auto-generated constructor stub
		}
		@Override
		//每隔一秒走一个人
		public void run() {
			// TODO Auto-generated method stub
			while(true) {
				synchronized (obj) {
					if (teamNum>0) {
						System.out.println(Thread.currentThread().getName()+"目前队伍还剩下"+teamNum+"人");
						teamNum--;
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}else {
						break;
					}
				}
			}
		}
	}
}

还可以使用sysynchronized修饰方法

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class ThreadTest {
	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		//创建执行任务的对象
		Team team = new Team();
		//创建三条线程
		Thread t1 = new Thread(team);
		Thread t2 = new Thread(team);
		Thread t3 = new Thread(team);
		//调用start方法
		t1.start();
		t2.start();
		t3.start();
	}
	/**
	 * 此类描述一队人,走出一个房间,房间内剩余多少人
	 * @author
	 *
	 */
	static class Team implements Runnable{
		//队伍初始人数
		private int teamNum = 20;
		//用来锁定使用权限的锁
		private Object obj = "lock";
		public Team() {
			// TODO Auto-generated constructor stub
		}
		@Override
		//每隔一秒走一个人
		public void run() {
			boolean flag = true;
			while(flag) {
				flag = out();
			}
		}
		private synchronized boolean out () {
			if (teamNum>0) {
				System.out.println(Thread.currentThread().getName()+"目前队伍还剩下"+teamNum+"人");
				teamNum--;
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}
				return true;
			}
			return false;
		}
	}
}

这两种方法创建的锁都是非公平锁,除了非公平锁还有公平锁,公平锁就是在一个对象被其他线程使用时,其他线程按照先后顺序排队获取使用权,非公平就是不管先来后到,只要使用权一经释放,同时开抢。

还有一种用来创建锁的方法,就是用Lock的子类ReentrantLock,在需要锁的地方调用lock方法,在需要解锁的地方调用unlock方法,在创建ReentrantLock对象时传入true即可创建一个公平锁

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest {
	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		//创建执行任务的对象
		Team team = new Team();
		//创建三条线程
		Thread t1 = new Thread(team);
		Thread t2 = new Thread(team);
		Thread t3 = new Thread(team);
		//调用start方法
		t1.start();
		t2.start();
		t3.start();
	}
	/**
	 * 此类描述一队人,走出一个房间,房间内剩余多少人
	 * @author
	 *
	 */
	static class Team implements Runnable{
		//队伍初始人数
		private int teamNum = 20;
		//自己创建一个锁
		private Lock l = new ReentrantLock();
		public Team() {
			// TODO Auto-generated constructor stub
		}
		@Override
		//每隔一秒走一个人
		public void run() {
			boolean flag = true;
			while(flag) {
				//锁定out方法
				l.lock();
				flag = out();
				//解锁
				l.unlock();
			}
		}
		private boolean out () {
			if (teamNum>0) {
				System.out.println(Thread.currentThread().getName()+"目前队伍还剩下"+teamNum+"人");
				teamNum--;
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}
				return true;
			}
			return false;
		}
	}
}

线程之间还可以进行通信:当一个线程在执行时,其他线程休眠,执行完毕后唤醒其他线程,之后自己进入休眠等待被唤醒。

来看这样一个例子:
public class ThreadTest {
	public static void main(String[] args) {
		Work w = new Work();
		new Leader(w).start();
		new Employee(w).start();
	}
}
/**
 * 存放员工的信息
 * 以及对应的上班时间
 * @author
 *
 */
class Work{
	private String time;
	private String name;
	
	/**
	 * 设置对应时间上班的人
	 * @param name
	 * @param time
	 */
	public void setEmployee(String name,String time) {
		this.name = name;
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.time = time;
	}
	/**
	 * 查看谁上班了
	 */
	public void getEmployee() {
		System.out.println("现在是"+time+","+name+"上班了");
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class Leader extends Thread{
	private Work w;
	
	public Leader(Work w) {
		super();
		this.w = w;
	}
	/**
	 * 指定小黑上晚班
	 * 小白上白班
	 */
	@Override
	public void run() {
		for(int i=0;i<20;i++) {
			if (i%2==0) {
				w.setEmployee("小黑","晚上");
			}else {
				w.setEmployee("小白","白天");
			}
		}
	}
}
class Employee extends Thread{
private Work w;
	
	public Employee(Work w) {
	super();
	this.w = w;
}

	/**
	 * 查看谁上班了
	 */
	@Override
	public void run() {
		for(int i=0;i<20;i++) {
			w.getEmployee();
		}
	}
}
输出结果(截取特殊部分):
现在是null,小黑上班了
现在是晚上,小白上班了
现在是晚上,小白上班了
现在是晚上,小白上班了
现在是白天,小黑上班了
可见,小黑在还没设置好时间时就已经上班了,然后小白上的晚班,小黑上的白班,数据错乱了

这时我们就可以加一个通讯的机制,调用当前对象的notifyAll()方法可以唤醒所有线程,调用wait()可以让该线程进入等待,因为这两个方法是Object里的所以可以直接调用。让员工在设置好时间后去工作,一个工作完另一个接班:

public class ThreadTest {
	public static void main(String[] args) {
		Work w = new Work();
		new Leader(w).start();
		new Employee(w).start();
	}
}
/**
 * 存放员工的信息
 * 以及对应的上班时间
 * @author
 *
 */
class Work{
	private String time;
	private String name;
	//work表示可以正常上班了
	private boolean work = false;
	/**
	 * 设置对应时间上班的人
	 * @param name
	 * @param time
	 */
	public synchronized void setEmployee(String name,String time) {
		if (!work) {
			this.name = name;
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.time = time;
			//设置好人员以及时间后调整work状态
			work = true;
			//唤醒Employee
			this.notifyAll();
			//进入等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	/**
	 * 查看谁上班了
	 */
	public synchronized void getEmployee() {
		if(work) {
			System.out.println("现在是"+time+","+name+"上班了");
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//工作完毕后调正工作状态
			work = false;
			//唤醒leader
			this.notifyAll();
			//进入等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

class Leader extends Thread{
	private Work w;
	
	public Leader(Work w) {
		super();
		this.w = w;
	}
	/**
	 * 指定小黑上晚班
	 * 小白上白班
	 */
	@Override
	public void run() {
		for(int i=0;i<20;i++) {
			if (i%2==0) {
				w.setEmployee("小黑","晚上");
			}else {
				w.setEmployee("小白","白天");
			}
		}
	}
}
class Employee extends Thread{
private Work w;
	
	public Employee(Work w) {
	super();
	this.w = w;
}

	/**
	 * 查看谁上班了
	 */
	@Override
	public void run() {
		for(int i=0;i<20;i++) {
			w.getEmployee();
		}
	}
}

另外线程也有容器,存放线程的容器叫线程池,线程池的部分后续补充......

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值