Java-多线程

一、线程和进程

进程-操作系统中的组成部分

所有运行中的任务对应一个进程,当一个程序进入内存运行时,就变成了一个进程,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位

进程的特征

1.独立性:进程是系统独立存在的实体,他可以拥有自己独立的资源,每个进程都有自己私有的地址空间,在没有经过进程本身的允许下,一个用户的进程是不允许访问其他进程的地址空间

2.动态性:进程与程序的区别是,程序只是一个静态的指令集和,而进程是一个正在系统中活动的指令集和,在进程中加入了时间的感念,进程具有自己的生命周期和各种活动状态,这些在程序中是不存在的

3.并发性:多个进程可以再单个处理器上并发执行,多个进程之间不会互相影响

并发性和并行性是两个概念

并行性:指在同一时刻,有多条语句指令在多个处理器上同时执行

并发性:同一时刻只有一条指令执行,但多个进程指令被快速轮换执行,是的在宏观上看起来是多个指令共同执行的效果

线程-进程的执行单元

特征

线程是进程的组成部分,一个进程可以有多个线程,一个线程必须有一个父进程

线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量

线程将不在拥有系统资源,他与父进程的其他线程共享所拥有的全部资源

线程是独立运行的,他不知道进程中是否还有其他线程存在

同一个进程中的多个线程是并发运行的

总结一句话:一个程序运行后至少有一个进程,一个进程可以包含多个线程,但至少包含一个线程

多线程的优势

1.进程在执行的过程中拥有独立的内存单元,但是多个线程共享内存,从而极大地提高了程序的运行效率

2.线程比进程有更高的性能,创建进程需要为进程重新分配系统资源,但创建线程代价小。

3.Java语言内置了多线程功能支持,从而简化了Java多线程编程

二、线程的创建和启动

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例

继承Thread类创建线程类

1.定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体代表线程要完成的任务,

因此把run()方法称为线程执行体

2.创建Thread子类的实例,即创建线程对象

3.调用线程对象的start()方法来启动多线程

package day10;

public class Test extends Thread {
    public Test(String name){
        super(name);
    }
    @Override
    //重写run方法,run方法就是线程的执行体
    public void run() {
        for(int i=0;i<100;i++){
            //直接调用Thread类的getName()方法返回当前线程的名字
            System.out.println(getName()+""+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            //Thread类提供了currentThread()方法获取当前线程
            System.out.println(Thread.currentThread().getName()+""+i);
            if(i==20){
                //创建线程
                Test t1 = new Test("线程1");
                Test t2 = new Test("线程2");
                Test t3 = new Test("线程3");
                Test t4 = new Test("线程4");
                Test t5 = new Test("线程5");
                //启动线程
                t1.start();
                t2.start();
                t3.start();
                t4.start();
                t5.start();
            }
        }
    }
}

注意的是:在默认的情况下,主线程的名字是main

实现Runable接口创建线程

1.定义Runable接口的实现(implements)类,并重写改接口的run()方法,该run方法同样是线程的执行体

2.创建Runable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象

创建新线程的方法

.1.new Thread(new Runable())

2.Test1 test1 = new Test1();new Thread(test1,"新线程")

package day10;

public class Test1 implements Runnable {
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                Test1 test1 = new Test1();
                new Thread(test1,"新线程1").start();
                new Thread(test1,"新线程2").start();
            }
        }
    }
}


//第二种
package day10;

public class Test3 {
    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<50;i++){
                    System.out.println(Thread.currentThread().getName()+" "+i);
                }

            }
        });
        t1.start();
    }
}

实现Callable<T>接口 

1.定义Callable接口的实现(implements)类,并重写改接口的run()方法

2.由于Thread和Ruhable都没有返回值,所有使用Callable(有返回值的),但Callable对象不能直接放到Thread中去(Thread并未提供),所以使用FutureTask<T>

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Test {
    //1.创建对象
    Callable<String> callable = new MyCallableTest(100);
    //2.生成FutureTask任务
    FutureTask<String> f1 = new FutureTask<>(callable);
    //3.交给线程池处理
    Thread thread=new Thread(f1);
    //4.启动线程
    thread.start();
}


import java.util.concurrent.Callable;

public class MyCallableTest implements Callable<String>{
    private int n;
    MyCallableTest(int n){
        this.n=n;

    }
    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i = 0; i <=n; i++) {
            sum+=i;
        }
        return "结果是:"+n;
    }
}

三、线程的生命周期

新建(New)就绪(Ready)运行(Runing)阻塞(Blocked)和死亡(Dead)

新建和就绪状态

当程序使用new关键字创建一个线程之后,该线程就处于新建状态,此时他和其他的Java对象一样,仅仅有Java虚拟机为其分配内存,只有当对象调用了start()方法之后,该线程才会处于就状态

package day10;

public class Test4 extends Thread {
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==5){
                new Test4().run();
                new Test4().run();
            }
        }
    }
}

上面的程序是直接调用了run()方法,该种结果只有一个线程:主线程main,

只能对处于新建状态的线程调用start()方法,否则会引发IllegalThreadStateException

运行和阻塞

1.如果处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程处于运行状态

2.当一个线程开始运行时,他不可能一直处于运行状态,线程在运行过程中需要被中断,目的是是其他的线程获得执行的机会

3.只有当线程调用了他的sleep()或yield()方法后才会被放弃所占用的资源

进入阻塞的条件

1.线程调用了sleep()方法主动放弃所占用的资源

2.线程调用了一个阻塞式IO方法,再该方法返回之前,该线程被阻塞

3.线程视图获取一个同步监视器,但该同步监视器正被其他线程所持有的

4.线程在等待某个通知(notify)

5.程序调用了线程的suspend()方法将该线程挂起--容易导致死锁

解除上面的阻塞,重新进入就绪状态

1.调用了sleep()方法的线程经过了指定的时间

2.线程调用的IO方法已经返回

3.线程成功的获取了视图取得的同步监视器

4.线程正在等待某个通知时其他线程发出了一个通知

5.处于挂起的线程被调用了resume()恢复方法

线程死亡

1.run()或call()方法执行完成,线程正常结束

2.线程抛出一个未捕捉的Exception或Error

3.直接调用该线程的stop()方法来结束该线程(容易发动死锁,不建议使用)

isAlive()方法是判断线程的状态

死状态无法再次启动线程

package day10;

public class Test4 extends Thread {
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        Test4 test4 = new Test4();
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==5){
                test4.start();
                System.out.println(test4.isAlive());
            }
            if(i>5&&!test4.isAlive()){
                //视图启动线程
                test4.start();
            }
        }
    }
}

四、同步锁

1.使用synchronized关键字修饰方法,代码块,但不能修饰构造器,成员变量

package day10;

public class Account {
    //封装账户的编号,账户余额的两个成员变量
    private String accountNo;
    private double balance;
    public Account(){}
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //重写hashCode()和equals()方法
    public int hashCode(){
        return accountNo.hashCode();
    }
    public boolean equals(Object obj){
        if(this==obj){
            return true;
        }
        if(obj!=null && obj.getClass()==Account.class){
            Account target=(Account)obj;
            return target.getAccountNo().equals(accountNo);
        }
        return false;
    }
}





package day10;

public class DrawThread extends Thread {
    //模拟用户账户
    private Account account;
    //所希望取得钱数
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
    //当多个线程修改共享的数据时,将涉及数据安全的问题
    @Override
    public void run() {
        //银行的余额大于索取的钱数
       //加同步锁
        synchronized (account) {
            if (account.getBalance() >= drawAmount) {
                System.out.println(getName() + "取钱成功,取出的余额是:" + drawAmount);
                //修改余额
                account.setBalance(account.getBalance() - drawAmount);
                System.out.println("余额是:" + account.getBalance());
            } else {
                System.out.println(getName() + "余额不足,取钱失败");
            }
        }
    }
}

//测试
package day10;

public class Test5 {
    public static void main(String[] args) {
        //创建一个用户
        Account lpf = new Account("1234567", 1000);
        //模拟两个线程取钱
        new DrawThread("lpf",lpf,600).start();
        new DrawThread("sjp",lpf,600).start();
    }

}

2.使用lock,一般常用的是ReentrantLock

package day10;

import java.util.concurrent.locks.ReentrantLock;

public class SuoTest {
    private final ReentrantLock lock=new ReentrantLock();
    //封装两个成员变量
    private String account;//编号
    private double balance;//余额
    //构造器
    public SuoTest(){}
    public SuoTest(String account,double balance){
        this.account=account;
        this.balance=balance;
    }

    public double getBalance() {
        return balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    //为了防止编号重复,重写hashCode和equals
    public int hashCode(){
        return account.hashCode();
    }
    public boolean equals(Object obj){
        if(this==obj){
            return true;
        }
        if(obj!=null && obj.getClass()==SuoTest.class){
            SuoTest st=(SuoTest)obj;
            return st.getAccount().equals(account);
        }
        return false;
    }
    //定义一个余额修改方法
    public void balanceUpdate(double updataBalance){
        //加锁
        lock.lock();
        try{
            if(balance>=updataBalance){
                System.out.println(Thread.currentThread().getName()+"取出的钱数为:"+updataBalance);
                try{
                    Thread.sleep(1);
                }catch (InterruptedException it){
                    it.printStackTrace();
                }
                //修改余额
                balance-=updataBalance;
                System.out.println(Thread.currentThread().getName()+"剩余余额为:"+balance);
            }
           else {
               System.out.println(Thread.currentThread().getName()+"余额不足,取出失败");
            }

        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SuoTest s1=new SuoTest("10001", 1000);
        s1.balanceUpdate(800);
        s1.balanceUpdate(800);
    }
}

五、线程相关常用方法

① wait()

wait(): 线程等待,会释放锁 (用于同步代码块或同步方法中,不然会报错),然后进入等待状态和notify()和notifyAll()一起使用。
wait(long timeout): 等待规定的时间,如果在规定时间内被唤醒就继续执行,如果超过规定时间也会继续向下执行。

public class TestWait implements Runnable {
    private Object lock;
    public TestWait(Object lock) {
        this.lock = lock;
    }
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        // 创建两个线程
        Thread t1 = new Thread(new TestWait(lock),"t1");
        Thread t2 = new Thread(new TestWait(lock),"t2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
            try {
                //唤醒等待中的线程,进入就绪状态
                lock.notify();
                
                //线程等待,释放锁
                lock.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "等待结束,继续执行");
        }
    }
}

运行结果:
t2准备进入等待状态                
t1准备进入等待状态
t2等待结束,本线程继续执行

运行结果不唯一,但是总会有一个线程执行不完。
以上运行步骤如下:
1.t2线程运行,调用notify()方法唤醒等待中的线程(现在等待队列中没有线程),然后调用wait()方法进入等待队列。
2.t1线程运行,调用notify()方法唤醒等待中的线程(这时唤醒了t2线程,因为只有等待队列中只有t2,所以一定是t2获取cpu使用权),然后wait()方法进入等待队列。
3.t2线程继续向下运行,然后结束,t1还在等待队列,没有被唤醒,所以t1执行不完。
注意:被唤醒的线程,会从等待队列,进入就绪状态,然后去竞争cpu的使用权。

② sleep(long timeout)

sleep(): 线程睡眠,不会释放锁(规定时间到了之后继续执行线程) 。一搬用于模拟网络延时。

public class TestSleep implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        // 创建两个线程
        Thread t1 = new Thread(new TestSleep(),"t1");
        Thread t2 = new Thread(new TestSleep(),"t2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
        try {
            //线程睡眠两秒之后,继续执行,不会释放锁。
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "等待结束,继续执行");
    }
}

运行结果:
t1准备进入睡眠状态
t2准备进入睡眠状态
t1睡眠结束,继续执行
t2睡眠结束,继续执行

以上结果不唯一。

③ join()

join(): 指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。底层是利用wait()方法实现。可以应用于必须多个线程完成才能执行主线程,比如:三个人去酒店吃饭,三个都到酒店才能上菜。
正常执行

public class TestJoin {

    public static void main(String[] args) {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    System.out.println("执行t1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("执行t2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
        System.out.println("执行main");
    }
}

运行结果:
执行main
执行t1
执行t2

让主线程(main)最后执行,让 t1 和 t2 先执行完成。

public class TestJoin {

    public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                try {
                    System.out.println("执行t1");
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                try {
                    System.out.println("执行t2");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
        t1.join(); //t1参与到当前线程(main)的执行中,所以主线程需要等待t1执行完成才会继续执行,但是t2不受影响、。
        System.out.println("执行main");
    }
}

运行结果:
执行t2
执行t1
执行main

或者

执行t1
执行t2
执行main

让 t1 和 t2 按顺序执行

public class TestJoin {

    public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                try {
                    System.out.println("执行t1");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                try {
                    System.out.println("执行t2");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t1.join(); //t1参与到当前线程的执行中,所以主线程需要等待t1执行完成才会继续执行,这是t2需要等待t1执行完毕之后才能执行。
        t2.start();
    }
}
运行结果:
执行t1
执行t2

④ yield()

yield(): 线程让步(正在执行的线程),会使线程让出cpu使用权,进入就绪状态。

public class TestYield {

    public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                System.out.println("执行t1");
                Thread.yield();//t1让出cpu使用权限,回到就绪状态
                System.out.println("执行结束t1");
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                System.out.println("执行t2");
                System.out.println("执行结束t2");
            }
        });
        t1.start();
        t2.start();
    }
}如果先执行t1,然后t1会让出线程,进入就绪状态,然后t2执行完成,t1再去竞争cpu的使用权,在继续执行。

⑤ notify()和notifyAll()

5.1、notify(): 随机唤醒一个在等待状态的线程,进入就绪状态。

public class TestNotify implements Runnable{
    static Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestNotify(), "t1");
        Thread thread2 = new Thread(new TestNotify(), "t2");
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000); //主线程睡眠,让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 获得了锁");
            System.out.println("唤醒前" + thread1.getName() + "状态是" + thread1.getState());
            System.out.println("唤醒前" + thread2.getName() + "状态是 " + thread2.getState());
            lock.notify(); // 随机唤醒一个在等待状中的线程
        }
        try {
            Thread.sleep(1000);//主线程睡眠,让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("唤醒后" + thread1.getName() + "状态是" + thread1.getState());
        System.out.println("唤醒后" + thread2.getName() + "状态是" + thread2.getState());
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                lock.wait(); // 进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 线程结束");
        }
    }
}

运行结果:
t1 开始执行
t2 开始执行
main 获得了锁
唤醒前t1状态是WAITING
唤醒前t2状态是 WAITING
t1 线程结束
唤醒后t1状态是TERMINATED
唤醒后t2状态是WAITING运行结果不唯一

由上面可以看出,notify()随机唤醒了一个线程,唤醒的是t1,也有可能唤醒的是t2。然后没被唤醒的线程一直处于等待状态,这样就会导致程序结束不了。

5.2、notifyAll(): 唤醒在等待队列的所有线程,进入就绪状态。

package com.navi.vpx;

public class TestNotify implements Runnable{
    static Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestNotify(), "t1");
        Thread thread2 = new Thread(new TestNotify(), "t2");
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000); //主线程睡眠,让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 获得了锁");
            System.out.println("唤醒前" + thread1.getName() + "状态是" + thread1.getState());
            System.out.println("唤醒前" + thread2.getName() + "状态是 " + thread2.getState());
            lock.notifyAll(); // 唤醒全部在等待状态的线程
        }
        try {
            Thread.sleep(1000);//主线程睡眠,让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("唤醒后" + thread1.getName() + "状态是" + thread1.getState());
        System.out.println("唤醒后" + thread2.getName() + "状态是" + thread2.getState());
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                lock.wait(); // 进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 线程结束");
        }
    }
}
运行结果:
t1 开始执行
t2 开始执行
main 获得了锁
唤醒前t1状态是WAITING
唤醒前t2状态是 WAITING
t2 线程结束
t1 线程结束
唤醒后t1状态是TERMINATED
唤醒后t2状态是TERMINATED

运行结果不唯一,有上面可得notifyAll()唤醒的是所有在等待状态的线程,使线程都进入就绪状态,然后竞争锁,最后全部执行完成,程序结束。

六、wait()和sleep()的区别?

① wait() 来自Object,sleep()来自Thread。

② wait()会释放锁,sleep()不会释放锁。

③ wait()只能用在同步方法或代码块中,sleep()可以用在任何地方。

④ wait()不需要捕获异常,sleep()需要捕获异常。

七、为什么 wait()、notify()、notifyAll()方法定义在 Object 类里面,而不是 Thread 类?


① 锁可以是任何对象,如果在Thread类中,那只能是Thread类的对象才能调用上面的方法了。

② java中进入临界区(同步代码块或同步方法),线程只需要拿到锁就行,而并不关心锁被那个线程持有。

③ 上面方法是java两个线程之间的通信机制,如果不能通过类似synchronized这样的Java关键字来实现这种机制,那么Object类中就是定义它们最好的地方,以此来使任何Java对象都可以拥有实现线程通信机制的能力。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值