• 已删除用户
Administrator
发布于 2021-06-15 / 449 阅读
0

Java多线程编程核心技术一

Java多线程编程核心技术

一、Java多线程技能

1.1停止线程

在Java中有以下3种方法可以终止正在运行的线程:

①使用退出标志,使线程正常退出,也就是当run方法完成后线程终止;

②使用stop方法强行终止线程,但是不推荐,该方法和stop、suspend、resume一样,已经被废弃;

③使用interrupt方法中断线程;

1.1.1判断线程是否使停止状态

Thread.java类中提供了两种方法:

①Thread.interrupted():测试当前线程是否已经中断,静态方法,会清除当前线程的状态(默认为false);

②this.isinterrupted():测试调用线程是否已经中断,实例方法,不会清除状态;

1.1.2异常法停止线程

通过第①种方法,判断线程,run方法完成后终止线程

public class MyThread extends Thread{
    public void run(){
        super.run();
        for(int  i=0;i<50000;i++){
            if(this.isinterrupted()){
                System.out.println("已经是停止状态了!我要退出!");
                break;
            }
        }
        System.out.println("run方法执行结束");
    }
}

通过抛出异常,直接结束线程,不执行后续代码:

public class MyThread extends Thread{
    public void run(){
        super.run();
        try{
          for(int  i=0;i<50000;i++){
            if(this.isinterrupted()){
                System.out.println("已经是停止状态了!我要退出!");
                throw new InterruptedException();
            }
        }
        System.out.println("run方法执行结束");  
        }catch(InterruptedException e){
            System.out.println("进去run方法中的catch了");
        }
    }
}

1.1.3在沉睡中停止

当线程处于sleep()状态,调用interrupt()方法,run方法中会立刻抛出异常,停止线程;

当线程先调用interrupt()方法,在执行sleep()方法时,也会立刻抛出异常,停止线程;

1.1.4暴力停止

调用stop()方法, 会抛出ThreadDeath异常;

1.2暂停线程

1.2.1suspend与resume方法的使用

通过suspend方法暂停线程,通过resume方法启动线程;

缺点:独占!!!,数据不同步

如果使用不当,极易造成公共的同步对象的独占,导致其他线程无法访问公共同步对象。

1.3yield()方法

yield()方法的作用是放弃当前的CPU资源,将它让给其他任务去执行。但放弃的时间不确定,有可能刚刚放弃,又马上获取到时间片;

1.4线程的优先级

优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象的任务;当然,优先级还具有“随机性”;

线程优先级分为1-10,数值越大,优先级越高;

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
}

1.5守护线程

在Java线程中有两种线程,一种是用户线程,一种是守护线程;它的特性是陪伴,当进程中不存在非守护线程了,则守护线程自动销毁;

典型的守护线程就是垃圾回收线程;通过this.setDaemon(true)来设置守护线程;

二、对象及变量的并发访问

2.1synchronized同步方法

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁;

2.1.2synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象锁的。

2.1.3出现异常,锁自动释放

当一个线程执行的代码出现异常时,其所持有的锁会自动释放;

2.2synchronized同步代码块

通过“对象监视器”,synchronized(对象),来保证同步和效率;

关键字synchronized还可以应用在static静态方法上,如果这样写,那就是对当前*.java文件对应的Class类进行持锁;

可以使用常量池作为加锁对象;

2.3死锁

Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。

可以通过jdk自带的工具来监测是否有死锁现象:

#通过jps找出所有java进程id
jps
#执行jstack命令,打印进程的线程状态
jstack -l id

2.4volatile关键字

关键字volatile的主要作用是使变量在多个线程间可见;强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

image-20210615153317013

使用volatile关键字增加了实例变量在多个线程之间的可见性。但是volatile关键字最致命的缺点是不支持原子性。

下面将关键字synchronized和volatile进行比较:

①关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法和代码块。

②多线程访问volatile不会发生堵塞,而synchronized会;

③volatile保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性;

非原子性说明:

public class MyThread extends Thread{
    volatile public static int count;
    private static void addCount(){
        for(int i=0;i<100;i++){
            count++;
        }
    }
    @Override
    public void run(){
        addCount();
    }
}

public class Run{
    public static void main(String[] args){
        MyThread[] mythreadArray=new MyThread[100];
        for(int i=0;i<100;i++){
            mythreadArray[i]=new MyThread();
        }
        for(int i=0;i<100;i++){
            mythreadArray[i].start();
        }
    }
}

测试结果,并非预料的10000,造成该结果的原因,就是因为count++是非原子性操作;

image-20210615160314347

可以使用synchronized实现同步,也可以通过AtomicInteger原子类进行实现。

三、线程间通信

3.1等待/通知机制的实现

方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁。即只能在同步方法或者同步块中调用wait()方法,否则抛出异常。

方法notify()也要在同步代码块中调用,该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出一个呈wait状态的线程,对其发出通知notify,并使其获得该对象的对象锁。需要说明的是,notify()方法后,不会立着释放锁,呈wait()状态的线程也并不能马上获取该对象锁,需要等到执行notify()方法的线程执行完,才会释放。而一个线程执行wait()方法后,立刻释放对象锁。

image-20210615164739239

当线程呈wait()状态时,调用对象的interrupt(),也会抛出InterruptedException异常。

3.1.1方法wait(long)的使用

带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒;

3.1.2生产者/消费者模式的实现

一生产与一消费
//生产者
public class B{
    private String lock;
    public B(String lock){
        this.lock=lock;
    }
    public void setValue(){
        try{
            synchronized(lock){
                if(!ValueObject.value.equals("")){
                    lock.wait();
                }
                String value=System.currentTimeMillis()+"_"+System.nanoTime();
                ValueObject.value=value;
                lock.notify();
            }
        }catch(InterruptException e){
            e.printStackTrace();
        }
    }
}
//消费者
public class C{
    private String lock;
    public C(String lock){
        this.lock=lock;
    }
    public void setValue(){
        try{
            synchronized(lock){
                if(ValueObject.value.equals("")){
                    lock.wait();
                }
                System.out.println("get 的值是 "+ValueObject.value)
                ValueObject.value="";
                lock.notify();
            }
        }catch(InterruptException e){
            e.printStackTrace();
        }
    }
}
//存储值得对象
public class ValueObject{
    public static String value="";
}
//测试代码
public class ThreadB extends Thread{
    private B b;
    public ThreadB(B b){
        this.b=b;
    }
    public void run(){
        while(true){
            b.setValue();
        }
    }
}
public class ThreadC extends Thread{
    private C c;
    public ThreadC(C c){
        this.c=c;
    }
    public void run(){
        while(true){
            b.getValue();
        }
    }
}

3.1.3通过管道进行线程间通信

class ReadData{
    public void readMethod(PipedInputStream in){
        try{
            System.out.println("开始读:");
            byte[] byteArray=new byte[20];
            int readLine = in.read(byteArray);
            while (readLine!=-1){
                String newData = new String(byteArray, 0, readLine);
                System.out.print(newData);
                readLine=in.read(byteArray);
            }
            System.out.println();
            in.close();

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

class WriteDate{
    public void writeMethod(PipedOutputStream out){
        try{
            System.out.println("开始写:");
            for(int i=0;i<100;i++){
                String outData=""+(i+1);
                out.write(outData.getBytes());
                System.out.print(outData);
            }
            System.out.println();
            out.close();

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
//测试代码
public class ThreadTrans2 {
    public static void main(String[] args) throws Exception {
        PipedInputStream in = new PipedInputStream();
        PipedOutputStream out = new PipedOutputStream();
        in.connect(out);
        ReadData readData = new ReadData();
        WriteDate writeDate = new WriteDate();
        ThreadWrite threadWrite = new ThreadWrite(writeDate, out);
        ThreadRead threadRead = new ThreadRead(readData, in);
        threadRead.start();
        threadWrite.start();
    }
}

class ThreadRead extends Thread{
    private ReadData readData;
    private PipedInputStream in;

    public ThreadRead(ReadData readData,PipedInputStream in){
        this.readData=readData;
        this.in=in;
    }
    @Override
    public void run() {
        readData.readMethod(in);
    }
}

class ThreadWrite extends Thread{
    private WriteDate writeDate;
    private PipedOutputStream out;
    public ThreadWrite(WriteDate writeDate,PipedOutputStream out){
        this.writeDate=writeDate;
        this.out=out;
    }

    @Override
    public void run() {
        writeDate.writeMethod(out);
    }
}

3.2方法join的使用

join(long)与sleep(long),都是等待一段时间,但join方法内部是通过wait(long)实现,所以具有释放锁的特点。

当b线程等待a.join()时,如果调用interrupt()方法,b会抛出异常,a正常执行;

3.3ThreadLocal的使用

ThreadLocal是JDK通过public static 变量的形式,实现了线程间私有数据的存放,底层其实是一个的map,key值为当前线程;