电脑里运行的一个程序成为称为一个进程,但是一个程序可以分成多个子程序来执行(我们称为线程),也就是一个进程中包含了多个线程(我们称为多线程),一个进程中至少包含一个线程。
一个进程中可以包含多个线程
多个线程中有些资源是共享的,有些是加锁的,有些是不共享的
创建一个线程的两种方法:
- 继承Thread类,重写run方法
- 实现Runnable接口,实现run方法
常用方法:
start
: 启动一个线程,系统会调用线程中的run方法
run
:必须要有的方法
setName
:给线程一个名字
isAlive
:线程是否活着
setPriority
: 设置线程优先级(1-10),默认是5,这个优先级是相对的
setDaemon
: 设置守护线程,默认为false。(守护线程,调用它的线程结束,自己也结束)
join
: (先执行此线程),join(100):优先执行100毫秒
currentThread
:得到当前线程
sleep
:让线程休眠一段时间,单位:毫秒
线程安全,线程同步
假设:
- 有一个银行账号,里面余额1000
- 两个线程(二个人)同时往账号里面存200
代码如下:
public class ThreadSync {
public static void main(String[] args) {
BankAccount ba = new BankAccount();
Thread husband = new BankThread(ba);
Thread wife = new BankThread(ba);
husband.start();
wife.start();
}
}
class BankAccount{
private double balance = 1000;
public boolean deposit(double add){
if(add <= 0)
return false;
else{
System.out.println("当前余额为:"+ balance);
balance += add;
System.out.println("新的余额为:"+ balance);
return true;
}
}
}
class BankThread extends Thread {
private BankAccount a;
public BankThread(BankAccount a){
this.a = a;
}
public void run(){
a.deposit(200);
}
}
结果是这样的:
也可能是这样的:
这是因为两个线程都对同一个资源(BankAccount)进行了操作,都在抢占同一个对象,这样就很容易出错,所以导致每次的输出结果可能不一样。
解决办法:
在BankAccount
中的deposit
方法的声明中加一个synchronized
关键字 :
public synchronized boolean deposit(double add)
表示该方法是线程安全的,当有一个线程调用这个方法时,会给它加一把锁,这时候其他线程就不能调用这个方法了,只有等到上一个线程调用完成之后才能访问。
其实也可以这样:
public /*synchronized*/ boolean deposit(double add){
if(add <= 0)
return false;
else{
synchronized(this){
System.out.println("当前余额为:"+ balance);
balance += add;
System.out.println("新的余额为:"+ balance);
}
return true;
}
}
用synchronized
来包裹一个代码块也是可以的,关于synchronized
的用法这里不再赘述,修改之后的运行结果为:
线程间的通信
wait:wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法。
线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。
要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
notify:notify()方法会唤醒一个等待当前对象的锁的线程。
和wait()方法一样,notify方法调用必须放在synchronized方法或synchronized块中。
线程的死锁
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。如上图所示,有两个线程都要获得Lock1和Lock2,当线程A获得了Lock1时,它会等待Lock2,但是Lock2已经被线程B获得,线程B在等待Lock1,双方都在等待对方的Lock解锁才能进行下一步操作,那么就会无限地等待下去,就造成了线程的死锁。
代码:
public class DeadLock {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
ThreadA ta = new ThreadA(lock1, lock2);
ThreadB tb = new ThreadB(lock1, lock2);
ta.start();
tb.start();
}
}
class ThreadA extends Thread {
Object lock1,lock2;
public ThreadA(Object lock1, Object lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
synchronized(lock1){
System.out.println("线程A拿到了lock1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2){
System.out.println("线程A拿到了lock1和lock2");
}
}
}
}
class ThreadB extends Thread {
Object lock1,lock2;
public ThreadB(Object lock1, Object lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
synchronized(lock2){
System.out.println("线程B拿到了lock2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock1){
System.out.println("线程B拿到了lock1和lock2");
}
}
}
}
运行结果:
可以看到程序运行到这里就会一直处于等待状态,并且会一直等下去。
解决:
解决的方式有很多种,这里就说一种比较简单的。上面的程序中线程A是先拿Lock1,而线程B是先拿Lock2,我们只要让两个线程拿的顺序是一样的就行了:
线程B变为:
class ThreadB extends Thread {
Object lock1,lock2;
public ThreadB(Object lock1, Object lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
synchronized(lock1){
System.out.println("线程B拿到了lock1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2){
System.out.println("线程B拿到了lock1和lock2");
}
}
}
}
运行结果:
以上就是一个简单的死锁,当然,我们在实际的编程中,大多数的死锁不会这么显而易见,还需仔细分析代码才能看出。
总结
Java单单一块多线程就可以写好基本书,以上只是对多线程的一个初步的理解,要想用好多线程,还需更深入的学习和研究。