Java 多线程

电脑里运行的一个程序成为称为一个进程,但是一个程序可以分成多个子程序来执行(我们称为线程),也就是一个进程中包含了多个线程(我们称为多线程),一个进程中至少包含一个线程。

一个进程中可以包含多个线程
多个线程中有些资源是共享的,有些是加锁的,有些是不共享的

创建一个线程的两种方法:

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,实现run方法

常用方法:
start : 启动一个线程,系统会调用线程中的run方法
run:必须要有的方法
setName:给线程一个名字
isAlive:线程是否活着
setPriority : 设置线程优先级(1-10),默认是5,这个优先级是相对的
setDaemon : 设置守护线程,默认为false。(守护线程,调用它的线程结束,自己也结束)
join : (先执行此线程),join(100):优先执行100毫秒
currentThread:得到当前线程
sleep:让线程休眠一段时间,单位:毫秒


线程安全,线程同步

假设:

  1. 有一个银行账号,里面余额1000
  2. 两个线程(二个人)同时往账号里面存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单单一块多线程就可以写好基本书,以上只是对多线程的一个初步的理解,要想用好多线程,还需更深入的学习和研究。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 3,002评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,515评论 1 15
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,394评论 3 87
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,805评论 12 45
  • python介绍 python是一个什么样的语言? 编译型和解释型静态语言和动态语言强类型定义语言和弱类型定义语言...
    b55e66e1710b阅读 306评论 0 0