多线程学习

线程概念

 

操作系统调用的是我们的进程,而一个进程里面包括许多任务,我们就需要将每个任务单独开始,这时光有进程是不够的,所以在进程里面在划分了线程,而线程才是cpu最小的执行单元,java内置了支持多线程的包。

 

操作系统分时调用多个进程,按时间片轮流执行每个进程,java多线程就是在操作系统给java程序的一个时间片内,在若干个独立的可控制的线程之间切换,每个进程都有一个内存区域,多个线程可以共享这部分的内存区域。

 

线程的4个生命周期

 

新建

当一个Thread类或其子类创建了一个对象,新生的线程处于新建状态,该线程已经有了内存空间和其它资源。线程创建之后只有内存资源,jvm线程调度器中还没有该线程,它必须调用start()方法通知JVM,这样JVM才会将该线程添加到线程调度器。

 

运行

jvm线程调度器中的线程分配到了时间片时,这个线程就可以继续执行run方法。在线程还没有执行前,这个线程是和主线程绑定的,因为需要主线程调用该线程的start()方法。在线程第一次被分配到了CPU资源的时候,线程就脱离了主线程,具有了自己的生命周期。

 

中断

1.JVMCPU资源从当前线程切换给其它线程(被动)。

2.线程执行期间执行了sleep类方法,使当前线程进入休眠状态,一旦执行该方法就立马让出CPU的使用权,当sleep时间完后,该线程在进入线程调度队列,等待被分配CPU资源。

3.线程调用了wait()方法,线程进入等待状态,等待状态的线程不会主动进入线程调度队列,排队等待CPU资源,必须由其他的线程调用notify()方法通知它,这个线程才会进入线程调度队列,排队等待CPU资源,

4.线程执行期间,某个操作进入了堵塞操作,如执行读写引起堵塞,进入了堵塞状态的线程不能进入线程调度队列,等待堵塞原因消除后,线程才进入线程调度队列。

 

死亡

1.线程正常完成了所有的工作,即执行完了run方法。

2.线程被提前强行终止,即强制run方法结束。

所谓了线程死亡,即线程所拥有的资源被释放了。

 

 

Thread的子类来创建线程

 

通过Thread类的子类创建线程需要子类重写run方法,run方法内的代码才是线程执行的实体,而父类的run方法体内没有任何代码。这种方式创建线程可以在线程种添加新的属性和方法,但是不允许继承其它的类。

 

 

 

import java.lang.Thread;

 

public class ThreadTest{

    public static void main(String args[]){

        Test thread1 = new Test("1",100);

        Test thread2 = new Test("2",200);

        thread1.start();

        thread2.start();

    }

}

 

class Test extends Thread{

    int n;

    Test(String threadName,int n){

        setName(threadName); //Thread类的类方法,给线程添加一个名称。

        this.n = n;

    }

    public void run(){

        try{

            for(int i =0;i<4;i++){

                //getName()Thread类的类方法,获取线程的名字。

                System.out.println("I am Thread:"+getName());

                //线程的类方法,让线程休眠n毫秒。该方法会触发InterruptedException异常

                sleep(n);

            }

        }catch(InterruptedException e){}

    }

}

 

 

 

Runnable接口来创建线程

这种方式创建线程,需要使用ThreadRunnable target)构造方法来创建,传入的参数是一个实现了接口Runnable接口的类创建的对象,这个对象称为目标对象,当轮到当前线程执行时,目标对象就会自动调用run方法。

 

 

 

 

import java.lang.Thread;

import java.lang.Runnable;

public class ThreadTest{

    public static void main(String args[]){

        Test target1 = new Test("1",100);

        Test target2 = new Test("2",200);

        Thread thread1 = new Thread(target1);

        Thread thread2 = new Thread(target2);

        thread1.start();

        thread2.start();

    }

}

 

class Test implements Runnable{

    int n;

    String name;

    Test(String threadName,int n){

        this.n = n;

        this.name = threadName;

    }

    public String getName(){

        return this.name;

    }

    public void run(){

        try{

            for(int i =0;i<4;i++){

                //getName()Thread类的类方法,获取线程的名字。

                System.out.println("I am Thread:"+getName());

                //线程的类方法,让线程休眠n毫秒。该方法会触发InterruptedException异常

                Thread.sleep(n);

            }

        }catch(InterruptedException e){}

    }

}

 

 

 

 

 

线程的优先级

java中的线程调度器来管理队列中的线程,调度器将线程分为1-10个优先级,默认的线程的优先级是5,可以使用Thread类的方法setPriority方法设置线程的优先级,该方法接收一个int类型参数。getPriority()方法返回线程的优先级,线程优先级高的线程是先执行的。不建议使用该方式来控制线程的执行,应该让每个线程都有执行的机会。

 

 

 

 

 

线程种一些常用的方法。

//让线程强制运行,强制运行期间其它的线程无法运行,必须等到该线程执行完毕。

public final void join() throws InterruptedException

//中断某个线程的执行

public void interrupt() 

//主动让出线程的执行权力

yield方法

//获取当前线程的名称

public final String getName()

//设置线程的名称

public final synchronized void setName(String name)

//获取线程的优先级

public final int getPriority()

//设置线程的优先级

public final void setPriority(int newPriority)

//判断当前线程是否是活着的

public final native boolean isAlive()

//让线程进入睡眠状态

sleep()

//挂起线程

Wait()

 

 

 

 

 

 

 

 

 

 

 

 

线程安全的问题

 

例如去银行取钱,银行内部肯定是:

1.判断你要取的钱是否小于你存的钱才能取钱成功,

2.取钱后将你的余额进行修改。

例如当两个人同时在不同银行对一个账户取钱(对应两个线程同时对共享资源进行操作),账户总共1000元,第一个人取了700元,但是在第2步还没执行(即余额还没有改时),第二个人再次对该账户取钱,因为此时的余额还没有改, 则这个人看到的余额还是1000,那么这个人还能取至少0-1000元,显然这是不合理的,这就出现了线程不安全的问题。

 

这个问题产生的原因就是java多线程环境下执行的不确定性,cpu可能随时在多个就绪的线程之间进行切换。线程安全问题实际就是多个线程对共享资源进行操作的问题。

 

import java.lang.Thread;

import java.lang.Runnable;

 

public class Xiancheng {

    public static void main(String args[]){

        Account account = new Account("laoyu",1000);

        DrawMoney d1 = new DrawMoney(account, 600);

        DrawMoney d2 = new DrawMoney(account, 600);

        Thread thread1 = new Thread(d1,"A");

        Thread thread2 = new Thread(d1,"B");

        thread1.start();

        thread2.start();

    }

}

 

 

class DrawMoney implements Runnable{

    private Account account;

    private int x;

    DrawMoney(Account account,int x){

        this.account = account;

        this.x = x;

    }

    public void draw(int money){

        try{

            if(money<this.account.getMoney()){

            System.out.println(Thread.currentThread().getName()+"取了:"+money+"");

            Thread.sleep(1000);

            this.account.changeMoney(money);

            System.out.println(Thread.currentThread().getName()+"余额:"+this.account.getMoney()+"");

            }else{

                System.out.println("您的余额不足");

            }

        }catch(InterruptedException e){}

    }

    public void run(){

        this.draw(x);

    }

}

class Account{

    private String accountid;

    private int money;

    Account(String accountid,int money){

        this.accountid = accountid;

        this.money = money;

    }

    public String getAccountid(){

        return this.accountid;

    }

    public int getMoney(){

        return this.money;

    }

    public void changeMoney(int change){

        this.money = this.money - change;

    } 

}

 

 

解决方法

 

同步方法

对共享资源进行操作的方法加上synchronized关键字修饰,被改关键字修饰的方法不管在任何时刻,当最先执行该方法的线程执行完该同步方法之前,其它的线程是不能执行该方法的。

public synchronized void draw(int money)

 

同步代码块

同步方法会将同步的范围扩大,这时候同步代码块会比较好,格式是synchronizedobj{};其中的obj一部是共享的资源或者this

synchronized(this.account){

                if(money<this.account.getMoney()){

                System.out.println(Thread.currentThread().getName()+"取了:"+money+"");

                Thread.sleep(1000);

                this.account.changeMoney(money);

                System.out.println(Thread.currentThread().getName()+"余额:"+this.account.getMoney()+"");

                }else{

                    System.out.println("您的余额不足");

                }

            }

 

lock对象

调用同步方法前需要获得线程锁,并且在同步代码执行完毕后需要释放锁

 

import java.lang.Thread;

import java.lang.Runnable;

import java.util.concurrent.locks.ReentrantLock;

import java.util.concurrent.locks.Lock;

public class Xiancheng {

    public static void main(String args[]){

        Account account = new Account("laoyu",1000);

        DrawMoney d1 = new DrawMoney(account, 600);

        DrawMoney d2 = new DrawMoney(account, 600);

        Thread thread1 = new Thread(d1,"A");

        Thread thread2 = new Thread(d1,"B");

        thread1.start();

        thread2.start();

    }

}

 

 

class DrawMoney implements Runnable{

    private Account account;

    private int x;

    private Lock lock = new ReentrantLock();

    DrawMoney(Account account,int x){

        this.account = account;

        this.x = x;

    }

    public void draw(int money){

        lock.lock();

        try{

            if(money<this.account.getMoney()){

            System.out.println(Thread.currentThread().getName()+"取了:"+money+"");

            Thread.sleep(1000);

            this.account.changeMoney(money);

            System.out.println(Thread.currentThread().getName()+"余额:"+this.account.getMoney()+"");

            }else{

                System.out.println("您的余额不足");

            }

        }catch(InterruptedException e){}

        finally{

            lock.unlock();

        }

    }

    public void run(){

        this.draw(x);

    }

}

class Account{

    private String accountid;

    private int money;

    Account(String accountid,int money){

        this.accountid = accountid;

        this.money = money;

    }

    public String getAccountid(){

        return this.accountid;

    }

    public int getMoney(){

        return this.money;

    }

    public void changeMoney(int change){

        this.money = this.money - change;

    } 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

wait()notify()notifyAll()实现线程之间通信。

wait()当线程调用wait方法会进入等待阻塞状态,只有通过其他的线程调用notify方法或notifyAll方法才会唤醒这个线程,让这个线程变为就绪状态。例如:在一个线程执行同步方法时,其中某个成员变量需要其它线程的辅助才能继续执行,这时就可以调用wait方法,让其它的线程调用该同步方法。

notify随机让一个wait的线程恢复。

notifyAll让所有的wait线程恢复。

 

wait方法会让当前线程处于等待状态,其它的线程可以调用该同步方法(释放了锁,正常只有等同步方法返回时,其它的线程才可以调用该同步方法)。

 

 

import java.lang.Thread;

import java.lang.Runnable;

 

public class Xiancheng{

    public static void main(String args[]){

        Runable r1 = new Runable();

        Thread t1 = new Thread(r1);

        Thread t2 = new Thread(r1);

        t1.start();

        t2.start();

    }

}

 

class Runable implements Runnable{

    T t = new T();

    public synchronized void run(){

        t.i=t.i+1;

        try{

            while(t.i<=1){

                System.out.println("我是第一个线程:"+t.i);

                wait();

           }

        }catch(InterruptedException e){}

        finally{

            System.out.println("我是第2个线程:"+t.i);

            notifyAll();

        }

    }

}

class T{

    public int i=0;

}

 

挂起和恢复线程

在实现了Runnable接口而穿件的线程时,可以在没有堵塞的线程中通过目标对象调用期同步方法,同步方法中分别是waitnotifyAll方法,来挂起或恢复线程。

 

 

import java.lang.Thread;

import java.lang.Runnable;

import java.lang.Object;

public class Xiancheng{

    public static void main(String args[]){

        Target target = new Target();

        Thread thread = new Thread(target);

        thread.start();

        while(target.getStop()==false){

            System.out.print(1);

        }

        target.restart();

    }

}

class Target implements Runnable{

    private int number=0;

    public boolean stop = false;

    public void run(){

        while(this.number<=8){

            this.number++;

            System.out.println("现在的number="+number);

            if(this.number==3){

                try{

                    this.stop=true;

                    System.out.println("线程被挂起了");

                    this.handUp();

                    System.out.println("线程恢复了");

                }catch(InterruptedException e){};

            }

        }

    }

    boolean getStop(){

        return this.stop;

    }

    synchronized void handUp() throws InterruptedException{

        wait();

    }

    synchronized void restart(){

        notifyAll();

    }

}

线程联合

A线程执行了B.join()方法,即A线程联合了B线程,那么A线程会一直等到B线程执行完毕后才会继续执行。

 

守护线程

void setDaemon(boolean on)方法可以将一个线程设置为守护线程(该方法需要在start方法之前调用),守护线程在用户线程执行完毕后就会全部关闭,

 

 

 

 

死锁的问题

在多线程执行的时候,多个线程对资源进行了占用,而多个线程之间由必须要对方控制的资源才能继续进行下去,多个线程都处于等待状态。例如一个线程A在控制着键盘的资源,另一个线程B控制的打印机的资源,而这时两者都需要对方的资源才能执行下去,这时候两个线程都无法继续进行下去,这时候就产生了死锁的问题。

 

死锁产生的4个必要条件

1.资源的排他性:任何时刻该资源都只有一个线程占据该资源。

2.不剥夺条件:线程在所获得的资源未使用完毕之前,不能被其它的线程剥夺。

3.请求保持条件:线程在拥有了某个资源又去申请另一个资源。

4.循环等待:存在一个线程拥有某个资源,而另一个线程又在申请该资源。

posted @ 2021-05-16 13:43  老余,他热爱生活  阅读(56)  评论(0)    收藏  举报