使用软件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();
}
}
}
另外线程也有容器,存放线程的容器叫线程池