Java 多线程 一篇全

进程:

进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。

 

 

 进程与线程的区别

  进程空间:数据区、代码区、栈区、堆区

  线程除了栈区是独立的之外都是共享的。

 

1、线程的生命周期

创建状态(Born)----当thread类对象被创建即处于创建状态。

就绪状态(Ready)----thread.start()进入就绪状态

运行状态:-----运行run()方法中的内容

阻塞-----1、线程进入syncronized同步语句,等待排斥锁、

  2、IO请求  

  3、sleep()方法,当睡眠时间到或者调用了interrupt()方法 时睡眠结束 

  4、为等待访问某个共享对象的信号变量,当前线程使用wait()进入等待状态。知道另一线程为共享对象调用notify()或notifyAll()时才结束。  

  5、程序时间片用完或使用了yield()方法进入就绪状态。

 

 

 run()完成后即进入死亡状态,程序退出。

 

而在官方文档中:线程可有6种状态:New新创建,Runnable可运行、Blocked阻塞、Waiting等待、TimeWaiting计时等待、Terminated被终止

通过getState()方法进行获取

 

2、线程的创建与执行

Thread的构造方法:

  Thread():创建一个线程对象,名称为系统指定的“Thread-1”、“Thread-2”。。。

  Thread(String threadName)创建一个线程对象,名字为threadName

  Thread(Runnable target)

  Thread(Runnable target,String ThreadName)

  Thread(ThreadGroup group,Runnable target,String ThreadName) 将线程加入线程组

1继承自Thread类:

public class FirstMulTest {
    public static void main(String args[]) {
        MyThread thread1=new MyThread();
        MyThread thread2=new MyThread();
        MyThread thread3=new MyThread();
        MyThread thread4=new MyThread();
        thread1.start();
        thread2.start();
        for(int i=1;i<10;i++) {
            System.out.println("Main Method runned:"+i);
        }
        thread3.start();
        thread4.start();
    }
}

class MyThread extends Thread{
    public void run() {
        for(int i=1;i<5;i++) {
            System.out.println(this.getName()+"has runned :"+i);
        }
    }
}

结果:

Main Method runned:1
Thread-1has runned :1
Thread-0has runned :1
Thread-0has runned :2
Thread-0has runned :3
Thread-0has runned :4
Thread-1has runned :2
Thread-1has runned :3
Main Method runned:2
Main Method runned:3
Main Method runned:4
Main Method runned:5
Main Method runned:6
Main Method runned:7
Thread-1has runned :4
Main Method runned:8
Main Method runned:9
Thread-2has runned :1
Thread-2has runned :2
Thread-3has runned :1
Thread-3has runned :2
Thread-3has runned :3
Thread-3has runned :4
Thread-2has runned :3
Thread-2has runned :4

 

2实现implements Runnable接口的 创建线程方法

继承了Runnable接口的类--创建一个对象,将该类对象作为参数传入Thread的构造参数。

public class RunnableTest {
    public static void main(String args[]) {
        Thread thread1=new Thread(new MyThreadRunnable("thread001"));
        Thread thread2=new Thread(new MyThreadRunnable("thread002"));
        thread1.start();thread2.start();
    }
}
class MyThreadRunnable implements Runnable{
    private String name;
    MyThreadRunnable(String threadname){
        this.name=threadname;
    }
    public void run() {
        for(int i=1;i<5;i++) {
            System.out.println(name+"has runned"+i+"times");
        }
    }
}
thread002has runned1times
thread002has runned2times
thread002has runned3times
thread001has runned1times
thread002has runned4times
thread001has runned2times
thread001has runned3times
thread001has runned4times

 

 

3、两种线程创建方法的区别

通过继承Thread类实现的线程-----创建了两个有着不同变量的对象

通过实现Runnable接口实现的线程-------可以共享变量

public class NotSharing {
    public static void main(String args[]) {

//通过Thread类继承来创建对象 Thread thread1
=new threadIndependent(); Thread thread2=new threadIndependent(); thread1.start();thread2.start(); } } class threadIndependent extends Thread{ private int ticket=6; public void run() { for(;ticket>=0;ticket--) { System.out.println(this.getName()+"has selled "+ticket+" tickets"); } } }

结果:两个ticket并不共享,创建了两个不同的实例

Thread-0has selled 6 tickets
Thread-1has selled 6 tickets
Thread-1has selled 5 tickets
Thread-0has selled 5 tickets
Thread-0has selled 4 tickets
Thread-0has selled 3 tickets
Thread-0has selled 2 tickets
Thread-1has selled 4 tickets
Thread-0has selled 1 tickets
Thread-1has selled 3 tickets
Thread-1has selled 2 tickets
Thread-1has selled 1 tickets
Thread-1has selled 0 tickets
Thread-0has selled 0 tickets

当使用实现Runnable建立线程

 ①用Runnable创建两个对象:

public class ShareVariable {
    public static void main(String args[]) {
        Thread thread1=new Thread(new RunThread("thread001"));    //如果new了两个对象,两个对象分别独立
        Thread thread2=new Thread(new RunThread("thread002"));
        thread1.start();thread2.start();
    }
}
class RunThread implements Runnable{
    String name;
    RunThread(String name){
        this.name=name;
    }
    private int tickNum=5;
    public void run() {
        for(;tickNum>=0;tickNum--) {
            System.out.println(name+"has selled "+tickNum+" tickets");
        }
    }
}

结果:两个对象的数据相互独立

thread001has selled 5 tickets
thread001has selled 4 tickets
thread002has selled 5 tickets
thread001has selled 3 tickets
thread001has selled 2 tickets
thread001has selled 1 tickets
thread001has selled 0 tickets
thread002has selled 4 tickets
thread002has selled 3 tickets
thread002has selled 2 tickets
thread002has selled 1 tickets
thread002has selled 0 tickets

一个失败的设计案例:

public class ShareVariable1 {
    public static void main(String args[]) {
        RunThread rt=new RunThread("RunThread");
        Thread thread1=new Thread(rt,"thread001");//两个线程“thread001”,“thread002”,对同一个目标操作
        Thread thread2=new Thread(rt,"thread002");
        thread1.start();thread2.start();
    }
}

结果:为什么会卖了两张num为5的票?原因是Thread002在执行代码的过程中读的的num是5,而thread001已经将它改为4了,如何防止这种情况下面再说。(此处先按下不表)

RunThreadhas selled 5 tickets
RunThreadhas selled 4 tickets
RunThreadhas selled 3 tickets
RunThreadhas selled 2 tickets
RunThreadhas selled 5 tickets
RunThreadhas selled 1 tickets
RunThreadhas selled 0 tickets

②数据共享

public class ShareVariable1 {
    public static void main(String args[]) {
        SellTicket rt=new SellTicket();
        Thread thread1=new Thread(rt,"thread001");    //三个线程共同操作一个Runnable target
        Thread thread2=new Thread(rt,"thread002");
        Thread thread3=new Thread(rt,"thread003");
        thread1.start();thread2.start();thread3.start();
    }
}
class SellTicket implements Runnable{
    private int ticket=10;
    
    @Override
    public void run() {
        String tname=Thread.currentThread().getName();  //得到当前线程的名字
        for(;ticket>0;) {
            System.out.println(tname +" sales ticket: "+ticket--);
        }
    }
}

结果:

thread002 sales ticket: 10
thread002 sales ticket: 7
thread002 sales ticket: 6
thread003 sales ticket: 8
thread003 sales ticket: 4
thread003 sales ticket: 3
thread003 sales ticket: 2
thread003 sales ticket: 1
thread001 sales ticket: 9
thread002 sales ticket: 5

 

4、Thread类的常见方法

静态方法:

static Thread currentThread():返回当前线程的引用

static void yield()  “礼让”,使当前线程进入就绪状态,让其他线程有机可乘。系统会使用此方法让用完时间片的线程回到就绪状态

static void sleep(int millsecond)  睡眠xx毫秒

static void sleep(int millsecond,int 纳秒)  睡眠xx毫秒+纳秒

 

非静态方法:

strat()、run()前文已介绍,当从run()返回时,线程结束

final void setName(String s)-----------设置线程的名字

final String getName ()-----------返回线程的名字

 

interrupt() 中断阻塞,进入就绪状态,将抛出InterruptException 

final boolean isAlive()----------线程是否已被启动

isInterrupted()----------判断线程是否被中断  (还有一个Interrupted()方法,后面解释,它的副作用是将线程的中断状态重置为false)

final join(【long miils】线程死亡或时间结束)----------,等待调用此方法的线程结束。当使用th.join(),要等th结束之后在执行此线程

getState()-----------进行获取线程可有6种状态:New新创建,Runnable可运行、Blocked阻塞、Waiting等待、TimeWaiting计时等待、Terminated被终止

 

 

所以阻碍线程的方法:yield()、join()、wait()、sleep()、synchronize同步锁、IO阻塞

不提倡在程序中使用:stop()、destory()停止/销毁线程方法、挂起/恢复线程的方法:suspend()、resume()。

 

 接下来一一介绍:

一个程序中至少有两个线程:主线程main、守护线程:系统自带的资源回收线程

 

sleep():

sleep()作用是让当前线程休眠,它是一个静态的方法,仅仅是让当前处于运行状态的线程进入休眠。Thread.sleep

    如果想让某个线程进入sleep,就要在它的代码块当中添加sleep语句。

  sleep语句加入后需要处理InterruptedException。

sleep-----------1、sleep时间结束,线程不抛出异常,并重新进入就绪状态

  ------------2、线程调用interrupt(),抛出InterruptedException, 线程进入就绪状态

程序示例:

public class BlockTest {
    public static void main(String args[]) {
        SleepThread st1=new SleepThread();
        SleepThread st2=new SleepThread();
        SleepThread st3=new SleepThread();
        Thread th1=new Thread(st1,"th1");
        Thread th2=new Thread(st2,"th2");
        Thread th3=new Thread(st3,"th3");        //创建三个线程对三个target操作
        th1.start();
        th2.start();
        th2.interrupt();        //main线程中 调用th2.interrupt(),当th2正好处于sleep中时,睡眠被打断,重新回到就绪状态
        th3.start();
    }
}
class SleepThread implements Runnable{
    String name;
    int sleeptime;
    public void run() {
        for(int i=1;i<5;i++) {
            name=Thread.currentThread().getName();
            System.out.println(name+" run "+i);
        try {    
            sleeptime=(int)(Math.random()*5000);        //随机睡眠0-4999毫秒
            System.out.println(name+"get into Sleep");    
            Thread.sleep(sleeptime);
        }
        catch(InterruptedException e) {
            System.out.println(name+"醒来");                //当线程睡眠被中断,打印“醒来”
        }
        }
    }
}

 结果:

th1 run 1
th2 run 1
th3 run 1
th2get into Sleep
th2醒来
th3get into Sleep
th1get into Sleep
th2 run 2
th2get into Sleep
th1 run 2
th1get into Sleep
th3 run 2
th3get into Sleep
th1 run 3
th1get into Sleep
th2 run 3
th2get into Sleep
th2 run 4
th2get into Sleep
th3 run 3
th3get into Sleep
th1 run 4
th1get into Sleep
th3 run 4
th3get into Sleep

 

join():

  join()方法运行期间,调用该方法的线程不会运行,直到join的线程完成。

例如:th1的run方法中出现th2.join()------th1直到th2结束之前不会运行。

 

public class JoinTest {
    public static void main(String args[]) {
        SleepThread st1=new SleepThread();
        SleepThread st3=new SleepThread();
        JoinThread jt = new JoinThread();
        Thread th1=new Thread(st1,"th1");
        Thread th3=new Thread(st3,"th3");        //创建th1\th3两个线程并运行
        th1.start();
        jt.start();                                //joinThread实例,其中包含join()
        th3.start();
        for(int i=1;i<=5;i++) {
            System.out.println("main method runs: "+i);
        }
        System.out.println("主线程结束");            //表示主线程的结束
    }
}

class JoinThread extends Thread{
    public void run() {
        SleepThread st2=new SleepThread();        //在joinThead中创建并运行th2线程
        Thread th2=new Thread(st2,"th2");
        String name=Thread.currentThread().getName();
        th2.start();
        System.out.println("Join Thread before");
        try{th2.join();}                        //等待th2执行完再执行JoinThread实例    
        catch(InterruptedException e) {            //join方法需要处理InterruptedException
            e.printStackTrace();
        }
        System.out.println("Join Thread after");
    }
}

结果:

main method runs: 1
th1 run 1
Join Thread before
main method runs: 2
th1 run 2
main method runs: 3
main method runs: 4
th1 run 3
th1 run 4
th3 run 1
main method runs: 5
th3 run 2
主线程结束
th3 run 3
th3 run 4
th2 run 1
th2 run 2
th2 run 3
th2 run 4
Join Thread after        //可以看到th2结束之后才开始继续jt线程

 

 

5、中断线程

在了解了上面的一些基本内容后,我们对线程有了一点点基本的了解。知道main也是一个线程,在main方法中去启用其他的线程,线程之间轮流运行。了解了sleep和join的简单用法。接下来将会继续深入。

在上文中,我们使用了interrupt()方法让线程重新开始运行并抛出interruptedException异常,这一过程是如何发生的呢?

(以下来自《java核心技术卷一》)

 

线程是如何终止的呢?1、run方法体中执行完最后一条语句,并经由return语句返回(在void方法中,return是隐式的,作用是使方法退出)

          2、方法中出现了没有捕获的异常

由于没有可以使线程强制终止的方法,(stop不安全而被停用),所以有了interrupt方法请求终止线程。这就是interrupt()的由来。

  当线程调用interrupt()的时候,线程的 boolean 中断状态   将被置位。要获得该状态,先调用静态的Thread.currentThread()获得当前线程,再调用isInterrupted() 方法。

 

  while(!Thread.currentThread().isInterrupted()){
    //中断状态为false的方法体
  }

但是如果线程被阻塞(sleep或wait),就无法检测中断状态,所以产生了InterruptedException,当阻塞线程调用interrupt()时会产生InterruptedException。需要注意的是,中断的目的不是使线程终止,而是引起它的注意,让被中断的线程决定如何响应这个中断。在中断状态被置位的时候调用sleep方法,不会休眠,且会清除中断状态并抛出InterruptedException,这就是用interrupt()将线程重新变成就绪状态的原理。

通过上述原理,我们就可以把处于阻塞状态的线程变成就绪状态,同时通过interruptedException 或者 中断状态 来实现终止线程。

void interrupt()----线程中断

static Boolean interrupted()------返回中断状态并将中断状态置为false

static Boolean isInterrupted()-------返回中断状态

 

6、线程优先级与守护线程

 抢占式系统给每个线程一个时间片来执行任务,时间片结束则根据线程优先级来考虑。  不要让程序依赖于优先级!!(Lunix的java虚拟机就不支持优先级)

void setPriority(int newPriority)-----设置线程优先级

int getPriority()-------获取线程优先级

static  int MIN_PRIORITY、MAX_PRIORITY、NORM_PRIORITY  1、5、10线程的优先级值

 

static void yield()-------将当前线程让步,给其他线程一个公平竞争的机会。

守护线程:

th.setDaemon(true):守护线程的唯一作用是为其他线程提供服务(System.gc()清理内存),当只剩下守护线程时,虚拟机退出。

 

未捕获异常处理器:

线程将未捕获的异常传给一个处理器。

该处理器属于一个实现Thread,UncaughtExcecptionHandler接口的类。这个接口只有一个方法:void uncaughtException(Thread t,Throwable e)

安装方法:setUncaughtExceptionHandler()、也可以用Thread的静态方法setDefaultUNcaughtExceptionHandler为所有线程安装一个默认的处理器

 

7、同步

如果没有同步会发生什么? 

程序实例:银行存储  

 SyncBankTest.java

public class SyncBankTest {
    public static final int NACCOUNTS=100;    //一家银行有一百个账户
    public static final int INITIAL_BALANCE=1000;    //账户的初始金额为
    public static void main(String args[]) {
        Bank bank=new Bank(NACCOUNTS,INITIAL_BALANCE);
        for(int i=0;i<NACCOUNTS;i++) {
            //创建一百个线程,将一百个账户中随机取出钱放入另一个账户
            TransferRunnable r=new TransferRunnable(bank,i,INITIAL_BALANCE);
            new Thread(r).start();
        }
    }
}

 

public class TransferRunnable implements Runnable{
    private Bank bank;
    private int fromAccount;
    private double maxAmount;
    private final int SLEEPTIME=10;
    
    /*
     * Constructor of The TransferRunnable
     * 
     * */
    public TransferRunnable(Bank b,int from,double max){
        bank=b;
        fromAccount=from;
        maxAmount=max;
    }
    public void run() {
        try {
            while(true) {
                int toAccount=(int)(bank.size()*Math.random());    //随机一个账户
                double amount=maxAmount*Math.random();    //随机一个数额
                bank.transfer(fromAccount, toAccount, amount);    //转钱
                Thread.sleep((int)(SLEEPTIME*Math.random()));    //随机sleep0-10秒
            }
        }
        catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * A bank with a number of bank accounts
 * 
 * */
public class Bank {
    private final double[] accounts;
    
    /*
     * Constructor of the bank
     * @param n the number of accounts
     * @param initialBalance the initial balance for each account
     * */
    public Bank(int n,double initialBalance){
        accounts=new double[n];
        for(int i=0;i<accounts.length;i++) {
            accounts[i]=initialBalance;
        }
    }
    /*
     * Transfer money from one accounts to another
     * 
     */
    public void transfer(int from,int to,double amount) {
        if(accounts[from]<amount) return;//当账号里没有这么多钱时不操作
        System.out.print(Thread.currentThread()+": ");
        accounts[from]-=amount;
        System.out.print(amount+" from "+from+" to "+to);
        System.out.println(" Total Balance:"+getTotalBalance());
        accounts[to]+=amount;
        
    }
    /*
     * to get the total money amount in the bank
     * @return
     */
    public double getTotalBalance() {
        double a=0.0;
        for(double i:accounts)
            a+=i;
        return a;
    }
    public int size() {
        return accounts.length;
    }
}

 

 

结果:完全是一片混乱,一个线程未打印完成 另一个已经开始打印了,总的钱数也是错误的

Thread[Thread-20,5,main]: 619.3615489776719 from 20 to 11 Total Balance:99380.63845102231
Thread[Thread-64,5,main]: 262.16742883578536 from 64 to 47 Total Balance:99737.8325711642
Thread[Thread-24,5,main]: 481.00432240684376 from 24 to 52 Total Balance:99518.99567759315
Thread[Thread-93,5,main]: Thread[Thread-78,5,main]: 25.074428395486347 from 78 to 25 Total Balance:99430.68566780332
544.2399038011893 from 93 to 1 Total Balance:99455.7600961988
Thread[Thread-20,5,main]: 619.8423194804546 from 20 to 10 Total Balance:99380.15768051955
Thread[Thread-52,5,main]: Thread[Thread-4,5,main]: 749.877662849608 from 4 to 92 Total Balance:98654.15188120895
Thread[Thread-40,5,main]: 472.7024550907262 from 40 to 38 Total Balance:98931.32708896785
Thread[Thread-99,5,main]: 45.187441587083875 from 99 to 91 Total Balance:99358.84210247148
Thread[Thread-92,5,main]: 166.03901954100232 from 92 to 80Thread[Thread-21,5,main]: 175.2156458808245 from 21 to 4 Total Balance:99062.77487863673
Thread[Thread-59,5,main]: 211.03539835336827 from 59 to 37 Total Balance:99026.95512616419
 Total Balance:99237.99052451758
Thread[Thread-69,5,main]: 835.0601714139708 from 69 to 75 Total Balance:98568.96937264461
Thread[Thread-98,5,main]: 33.06069263079658 from 98 to 12 Total Balance:99370.96885142781
Thread[Thread-10,5,main]: 627.7708700542092 from 10 to 16 Total Balance:98776.25867400438
Thread[Thread-95,5,main]: 867.0501483830784 from 95 to 49 Total Balance:98536.97939567553
Thread[Thread-61,5,main]: 355.9639510337218 from 61 to 31 Total Balance:99048.06559302489
595.9704559414303 from 52 to 36 Total Balance:99404.0295440586
Thread[Thread-98,5,main]: 134.13991898098388 from 98 to 20 Total Balance:99865.86008101904

  

问题在于

        System.out.print(Thread.currentThread()+": ");
        accounts[from]-=amount;
        System.out.print(amount+" from "+from+" to "+to);
        System.out.println(" Total Balance:"+getTotalBalance());
        accounts[to]+=amount;

不是原子操作,在执行过程中不是连续执行的,导致另一个线程读到的accounts是未修改之前的数值。

Java提供了两种机制来防止代码块收到干扰:synchronized关键字和ReentrantLock类(java5.0)(java.util.concurrent框架)

 

①锁对象

 

java.util.concurrent.locks.Lock 5.0

  void lock()获取锁

  void unlock() 释放锁

 

java.util.concurrent.locks.ReentrantLock 5.0

  ReentrantLock(【boolean】)  构建一个非公平(带参数为公平)的可重入锁。ReentrantLock是Lock的唯一子实现

 

 

mylock.lock();

try{

  临界区

}

finally{

  mylock.unlock  //即使错误发生也会解锁

}

当其他线程调用该代码。遇到了mylock.lock(),访问就会被拒绝,因为mylock已经被使用该临界区的线程占用了。其他调用线程因此被阻塞,直到mylock,unlock()。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;    //ReentrantLock是Lock的唯一实现

public class Bank {
    。。。
    private Lock bankLock=new ReentrantLock();

    public void transfer(int from,int to,double amount) {
        。。。
        
        bankLock.lock();        //加锁
        try {
            System.out.print(Thread.currentThread()+": ");
            accounts[from]-=amount;
            System.out.print(amount+" from "+from+" to "+to);
            System.out.println(" Total Balance:"+getTotalBalance());
            accounts[to]+=amount;
        }
        finally {
            bankLock.unlock();    //解锁
        }
    }    

 

上述代码中的RennTrantLock bankLock 是在Bank类中,也就是说,每实例化一个Bank对象,都会产生一个专属于该对象的bankLock。如果两个线程访问同一个bankLock,他们之间是顺序执行的。但如果访问的是不同的Bank对象中的bankLock,则他们之间不会有任何影响(锁是非静态的,属于对象而不是类)。

ReentrantLock锁是可重入的(关于可重入锁有更深层的设计,和死锁有关):即线程可以重复申请已获得的锁(lock()之后再lock())。JVM会记录锁的获得次数(持有计数hold count),线程在每一次lock之后都要unlock。如果lock两次而只是unlock一次则还有一个锁没有解锁。

例如:transfer 方法调用getTotalBalance(),会使bankLock再次封锁,计数为2,当getTotalBalance()方法结束,计数退回1。当transfer()方法结束,计数退回0。线程释放锁。

 

!!不是把unlock()方法放在finally中就万无一失。在临界区中的代码发生异常就会直接跳到finally,不能确定临界区中的代码处于何种状态,所以要时刻注意。

 

方法ReentrantLock(【boolean isfair】)默认为非公平锁,参数true后为公平锁(效率低)。详情请参考公平锁方面的内容。

 如果使用锁,就不能使用带资源的try语句(此处的资源是指那些必须在程序结束时显式关闭的资源,比如数据库连接,网络连接等)

 

② 条件对象

 但上面的程序还是有问题:当线程进入临界区,却发现某一条件满足之后它才能执行。这就需要有效管理那些已获得锁却不能工作的线程

例如:我们尝试用锁保护转账transfer方法

    public void transfer(int from,int to,double amount) {

        bankLock.lock();        //加锁

        try {
              while(account[from]<amount){
                  //wait
                  ....
             }
             //tansfer funds
             ......
        }
        finally {
            bankLock.unlock();
        }
        
    }                                                                    

出现问题了:当一个线程加锁之后,发现账户上没有足以转账的金额,于是便进入while循环,等待账户上有足够的钱可以转账,但存钱的操作已经被锁住了,无法存钱,出现了死锁,所以就需要条条件对象来解决这个问题。

 

java.util.concurrent.locks.Lock 5.0

  Condition newCondition() 返回一个与该锁相关的条件对象

Condition 类中的方法:

  void await()将该线程放到条件的等待集

  void signalAll()解除该条件的等待集的所有阻塞状态

 

class Bank{
    private Condition sufficientFunds;
    ...
    public bank(){
       .....
        sufficientFunds=bankLock.newCondition();
    }
}

创建了一个名为“有充足的钱“的Condition条件对象。该条件对象与bankLock相关(由bankLock的newCondition()创建)。

 

这个对象是如何运作的?

当transfer中余额不足时,调用sufficientFunds.await()。获得该锁的线程进入到sufficientFunds的”等待集“中并且释放锁,处于阻塞状态。只有当另一个线程调用 sufficientFunds.signalAll() 的时候,所有等待集中的线程进入就绪状态,重新竞争这个锁bankLock。当重新获得锁之后,会从被阻塞的位置开始继续运行。

通常使用:while(!满足条件){ condition.await; }

另外还有一个signal()方法。随机唤醒一个线程,不是很靠谱。

 

8、Synchronized关键字

在前面的一节中已经充分揭示了Lock和Condition机制。实际上这两个接口是提供给程序设计人员的高度定制的锁机制有一种更加简便的方法能轻松使用锁,那就是synchronized 关键字。

当一个方法被声明为synchronized的时候,当调用该方法时线程会获得内部的对象锁,

Object类  final  wait()、notifyAll()、notify()

对应Condition的await、signalAll、signal()。

public class NewBank {
    public void synchronized transfer(int from,int to,double amount)throws InterruptedException {
        while(accounts[from]<amount) 
                wait();                //当账号里没有这么多钱时不操作
        accounts[from]-=amount;
        accounts[to]+=amount;
        notifyAll();
        }
    public synchronized double getTotalBalance() {
        ...
    }
}

将静态方法声明synchronized是合法的。

但内部锁和条件仍然存在问题:不能中断正在试图获得锁的线程、试图获得锁时不能设定超时,每个锁只有单一条件。

在实际的代码中,不应积极使用Lock\Condition或synchronized。而是使用java.util.concurrent包中的机制(如阻塞队列)来处理所有的加锁。synchronized是次要选择,Lock、Condition特殊需要才用(上文提到仅用于理解锁机制)

wait(【毫秒】,【纳秒】)。等待指定时间或等notify。(计时等待状态的一种进入方式)

 

题外话:1同步阻塞(客户端锁定)不推荐使用;2监视器是一种成功的多线程安全解决方案,但java中未得到好的实现。(锁和条件不是严格的面向对象,所以用起来会觉得非常别扭)

3java.util.concurrent.atomic包提供很多原子性操作,开发并发工具的设计人员可以考虑使用  4ThreadLocal辅助类可以帮助建立局部的非共享变量(为每个线程建立彼此不共享的变量)而不用很大的开销;

线程局部变量的实现:

假设有一个静态的类:

public static final SimpleDataFormat dataFormat=new SimpleDataFormat("yyyy-mm-dd")

如果两个线程都执行:String dateStamp=dataFormat.format(new Data());  结果会一片混乱,用同步又开销过大。那么可以考虑使用线程局部变量ThreadLocal

public static final ThreadLoval<SimpleDataFormat> dataFormat=new ThreadLocal<SimpleDataFormat>{

  protected SimpleDataFormat initialValue(){

    return new SimpleDataFormat("yyyy-mm-dd");

  }

}

 如果要访问具体的方法:String dataStamp=dataFormat.get().format(new Data())

 当首次调用get()时,会调用Initialize来得到这个值。

 

 

9、Volatile域

  通使用锁和条件来管理我们的线程和代码块,好像已经万无一失了。当我们不使用线程或同步的时候,在现代的处理器和编译器中出错几乎是必然的(参考第7节中不使用同步的例子)。为什么?

1多处理器的计算机能够暂时在寄存器或本地内存缓冲区中保存内存的值,线程可能取值的时候取到好几个不同的值     2编译器会改变指令执行的顺序使程序吞吐量最大化。锁可以保护这部分代码(具体的底层实现机制参考JSR133 java的内存模型和线程规范)

  同步的开销较大,虽然安全,但为了读写一个或两个实例域就显得不划算。于是java有一种免锁机制  ----volatile关键字:

  例如:

private Boolean done;

public synchronized boolean isDone() {return done;}

public synchronized setDone(boolean b) {done=b;}

这样的话isDone和setDone都可能阻塞,所以将域声明为volatile比较合理。

private volatile Boolean done;

public  boolean isDone() {return done;}

public  setDone(boolean b) {done=b;}

这样虚拟机和编译器就知道该域可能被另一个线程并发更新。如果仅是赋值的话可以考虑使用volatile关键字。

 

10、读写锁

  java.util.concurrent.locks包定义了两个锁类:ReentrantLock和ReentrantReadWriteLock

当读的时候是可以共享访问的,写的话就必须排斥其他操作。

步骤:1构造读写锁对象:

private ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();

2分离读锁、写锁

private Lock readLock()=rwl.readLock();

private Lock writeLock () = rwl. writeLock();

3 对读方法加读锁

public boolean getBool(){

   readLock.lock();

   try{

  }

  finally{

   readLock.unlock();

  }

}

4对写方法加写锁:

public void setBool(boolean b){

   writeLock.lock();

   try{

  }

  finally{

   writeLock.unlock();

  }

}

Java.util.concurrent.locks.ReentrantReadWriteLock  5.0

  Lock readLock()

 

11、阻塞队列

    阻塞队列提供了一种实现多线程的高层次实现方法,线程安全的队列类的实现者不能不考虑锁和条件,但使用阻塞队列就不需要考虑了。

多线程并发处理,线程池要用到阻塞队列。对于许多现成问题,可以通过使用一个或多个队列将其形式化,用生产者消费者模型,java文档示例:

 class Producer implements Runnable {
  //生产者类

  //定义一个队列
private final BlockingQueue queue;       Producer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { queue.put(produce()); }        //将“产品”对象推入队列之中 } catch (InterruptedException ex) { ... handle ...} } Object produce() { ... } } class Consumer implements Runnable { private final BlockingQueue queue; Consumer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { consume(queue.take()); }        //从队列中取出一个“产品” } catch (InterruptedException ex) { ... handle ...} } void consume(Object x) { ... } } class Setup { void main() { BlockingQueue q = new SomeQueueImplementation(); Producer p = new Producer(q); Consumer c1 = new Consumer(q); Consumer c2 = new Consumer(q); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); }

 

所有的队列:

ArrayBlockingQueue :由数组结构组成的有界阻塞队列。

LinkedBlockingQueue :由链表结构组成的有界阻塞队列。

PriorityBlockingQueue :支持优先级排序的无界阻塞队列。

DelayQueue:使用优先级队列实现的无界阻塞队列。

SynchronousQueue:不存储元素的阻塞队列。

LinkedTransferQueue:由链表结构组成的无界阻塞队列。

LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

 

ArrayBlockingQueue(int capacity)构建一个带有指定容量和公平性设置的阻塞队列,该队列用循环数组实现

LinkedBlockingQueue ()、LinkedBlockingDeque()构造一个无上限的阻塞队列或双向队列,用链表实现

LinkedBlockingQueue (int capacity)、LinkedBlockingDeque(int capacity)有固定容量

DelayQueue()无界、阻塞时间有限,只有延迟超过时间的元素可以从队列中移出,long getDelay(Time unit)得到该对象的延迟。

 

  Throws exception Special value Blocks Times out
Insert add(e) offer(e) put(e) offer(e, time, unit)
Remove remove() poll() take() poll(time, unit)
Examine element() peek() not applicable not applicable

examine是检查方法。

另一实例(自己写的)

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class BlcokingQueueTest {
    public static void main(String args[]) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(20);

        new BankProducer(queue).start();
        new BankProducer(queue).start();
        new BankProducer(queue).start();
        new BankConsumer(queue).start();
        new BankConsumer(queue).start();
    }
}

class BankConsumer extends Thread{
    private final BlockingQueue<Integer> queue;
    
    public BankConsumer(BlockingQueue<Integer> queue){
        this.queue=queue;
    }
    public void run() {
        while(true) {
            try {
                Integer amountTake=this.queue.take();
                System.out.println("Consumer took: "+amountTake);
                Thread.sleep(1000);
            }
            catch(InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}

class BankProducer extends Thread {
    private final BlockingQueue<Integer> queue;
    
    public BankProducer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                Integer saveAmount= (int)(500*Math.random());
                this.queue.put(saveAmount);
                System.out.println("Stock in : "+saveAmount);
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}

结果:

Stock in : 341
Stock in : 359
Stock in : 353
Consumer took: 341
Consumer took: 359
Stock in : 177
Stock in : 370
Stock in : 117
Consumer took: 353
Consumer took: 177

 

别的内容(水平有限):

1、多线程并发修改数据结构容易破坏这个结构,可以用锁机制来保护,还有一种方法是使用线程安全的集合

java.util.concurrent 提供映射表ConcurrentHashMap、ConcurrentSkipListmap有序集、队列ConcurrentLinkedQueue。

2、Callable 和 Future,与Runnable相似。

3、执行器与线程池

4、同步器

posted @ 2020-10-28 01:55  浪里薯条  阅读(90)  评论(0)    收藏  举报