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单单一块多线程就可以写好基本书,以上只是对多线程的一个初步的理解,要想用好多线程,还需更深入的学习和研究。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

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