java基础多线程

首先什么是线程(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    程序卡在这里,产生死锁。

经典问题:生产者消费者问题

日后写一篇博客介绍

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值