首先什么是线程(Thread)?线程是在进程里的,那什么是进程(Process)?把电脑的任务管理器打开就可以看见一个一个的进程(就是运行中的程序),由CPU按照进程调度算法进行资源分配,而线程就是进程里的一个个任务。简而言之 ,进程就是运行中的程序,线程就是其中的执行单元。比如打开QQ,QQ就是一个进程,你要和朋友聊天,和A朋友聊天就是一个A线程,同时也在和B朋友聊天,这就是一个B线程,两个线程在同时运行,各自完成各自任务。其实在java程序中main()方法就是一个线程,而且是叫做主线程。
多线程即在同一时间,有多个线程在做事情。
创建线程有三个方法:1.继承Thread类 2.实现Runnable接口 3.匿名类。一般选用第二种,因为第一种线程任务一起,第二种线程任务单独分离出来,与线程对象解耦,程序扩展性更好,避免了单继承的局限性。更加符合面向对象,所以一般选第二种。来看下代码
1.继承Thread类
/**
* CreateThread01继承Thread类,重写run方法
* @author Administrator
*
*/
public class CreateThread01 extends Thread{
public void run(){
for(int i = 0;i<5;i++){
System.out.println("搬运第"+i+"个货物");
}
}
}
2.实现Runnable接口
/**
* 实现Runnable接口,重写run方法,之后添加到Thread中
* @author Administrator
*
*/
public class CreateThread02 implements Runnable {
@Override
public void run() {
for(int i = 0;i<7;i++){
System.out.println("t2搬运第"+i+"个货物");
}
}
}
测试类(包括 3.匿名类方法)
public class ThreadTest {
public static void main(String[] args) {
//1.继承Thread类
CreateThread01 t1 = new CreateThread01();
//启动线程
t1.start();
System.out.println("--------------");
//2.实现Runnable接口
CreateThread02 run = new CreateThread02();
Thread t2 = new Thread(run);
t2.start();
System.out.println("--------------");
//3.匿名类
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<6;i++){
System.out.println("t3搬运第"+i+"次货物");
}
}
});
t3.start();
}
}
线程类常用方法小结:
new Thread(Runnable target) | 构造方法 |
new Thread(Runnable target,String name) | 构造的同时指定线程名 |
普通方法 | |
CurrentThread() | 返回当前正在执行的线程对象的引用 |
getId() | 返回当前线程的表示符 |
getName() | 返回当前线程的名称 |
isAlive() | 测试当前线程是否处于活动状态 |
sleep() | 休眠指定毫秒数后继续执行(带着资源休眠) |
join() | 先执行该线程,其他的等待他执行完 |
join(Long ms) | 等待该线程终止的最大时间为ms |
setPriority(int newPriority) | 更改线程的优先级 取值一般有Thread.MAX_PRIORITY 最高优先级10 Thread.NORMAL_RIORITY默认优先级 5 Thread.MIN_PRIORITY最低优先级 1 |
setDaemon(true) | 设置当前线程为守护线程,守护其他非守护线程执行完,自己也就停止执行 |
下图是关于线程的生命周期
我们创建多线程,因为多线程同时执行,所以各自都在抢夺进程资源,这样容易发生混乱,也就引发了线程安全问题。比如我们玩LOL,盖伦在基地,每次回血一点,同时艾希在攻击他,每次造成一点伤害,这样盖伦回血1000次,艾希攻击1000次,按道理说,最后盖伦血量应该不变,但是在不同步的情况下,对同一个数据多个线程操作的情况下,容易产生脏数据(错误数据)。
就比如现在盖伦(英雄类对象)有900滴血。
两个线程,回血线程获得盖伦对象血量,并且为其加一。伤害线程获得盖伦对象血量,并且为其减一。
回血线程获得资源,读取到血量900,准备加一滴,可是还没来得及修改。
伤害线程抢了资源,读取到血量数据900,然后减一滴,900-1,盖伦血量被修改为899,
这时加血线程来了,为原来读取到的900加1点,盖伦血量被修改为901,就这样最后盖伦血量变成了901,也就是脏数据。
英雄类
public class Hero {
private int Blood;
public Hero(int blood) {
super();
Blood = blood;
}
//回血
public void recover(){
this.Blood++;
}
//被伤害
public void hurted(){
this.Blood--;
}
public int getBlood() {
return Blood;
}
public void setBlood(int blood) {
Blood = blood;
}
}
测试类:
public class SyncTestDemo {
public static void main(String[] args) {
int n = 10;
// 创建盖伦对象,血量900
Hero geeren = new Hero(900);
// 回血线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < n; i++) {
geeren.setBlood(geeren.getBlood()+1);
System.out.println("t1加一"+geeren.getBlood());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
t1.start();
// 伤害线程
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < n; i++) {
geeren.setBlood(geeren.getBlood()-1);;
System.out.println("t2减一"+geeren.getBlood());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
t2.start();
try {
//等待t1,t2线程先走完
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("最终血量"+geeren.getBlood());
}
}
多运行几次发现最终血量不是900,而是901,899之类的脏数据。
这个时候解决这个问题需要同步(synchronized)锁,就是一个时间内只允许一个进程对数据修改,其他进程必须等待这个进程释放对数据的持有才能对这个数据修改。使用synchronized有三种方式 1.创建一个锁 2.使用合适对象作为同步对象 3.修饰在方法上,比如修饰在回血线程,此时同步对象就是this(当前对象)
上代码。
1.创建一个锁对象或者锁接口
import java.util.concurrent.locks.ReentrantLock;
public class SyncTestDemo {
public static void main(String[] args) {
int n = 10;
// 创建盖伦对象,血量900
Hero geeren = new Hero(900);
//创建ReentrantLock类或者Lock接口
ReentrantLock retLock = new ReentrantLock();
// 回血线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < n; i++) {
//上锁
retLock.lock();
geeren.setBlood(geeren.getBlood()+1);
System.out.println("t1加一"+geeren.getBlood());
//解锁
retLock.unlock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
t1.start();
// 伤害线程
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < n; i++) {
//上锁
retLock.lock();
geeren.setBlood(geeren.getBlood()-1);;
System.out.println("t2减一"+geeren.getBlood());
//解锁
retLock.unlock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
t2.start();
try {
//等待t1,t2线程先走完
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("最终血量"+geeren.getBlood());
}
}
2.使用合适对象作为锁
就是将1.的代码改为
t1改为
//上锁
synchronized (geeren) {
geeren.setBlood(geeren.getBlood()+1);
System.out.println("t1加一"+geeren.getBlood());
}
t2改为
//上锁
synchronized (geeren) {
geeren.setBlood(geeren.getBlood()-1);;
System.out.println("t2减一"+geeren.getBlood());
}
3.作为修饰符加在方法上
将Hero的recover(),hurted()修饰为synchronized,这样子其实就是将this (当前对象)作为锁使用
Hero类
//回血
public synchronized void recover(){
this.Blood++;
}
//被伤害
public synchronized void hurted(){
this.Blood--;
}
我们同步对象后,当多线程业务复杂时,就有可能产生死锁(DeadLock)。死锁概念就是比如线程一首先占用了对象A,线程二首先占有对象B,接着线程一试图占有对象B,线程二试图占有对象A。这样造成线程一等待线程二释放对象B,而线程二在等待线程一释放对象A,两者你等我,我等你,一直等下去,产生死锁,死锁我们是解决不了的,只能尽量避免这种情况的发生,比如记录线程执行时间,当线程执行时间超过规定时间,要进行检查是否死锁。
public class TestDeadLock {
public static void main(String[] args) {
// TODO Auto-generated method stub
Object obj01 = new Object();
Object obj02 = new Object();
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (obj01) {
System.out.println("t1持有obj01");
synchronized (obj02) {
System.out.println("t1持有obj02");
}
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
synchronized (obj02) {
System.out.println("t2持有obj02");
synchronized (obj01) {
System.out.println("t1持有obj01");
}
}
}
};
t1.start();
t2.start();
}
}
结果 t1持有obj01
t2持有obj02 程序卡在这里,产生死锁。
经典问题:生产者消费者问题
日后写一篇博客介绍