Java高级----多线程

一、进程和线程

1、概念

进程,是计算机中正在运行的程序,是系统进行资源分配的基本单位。目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分。以前的单核CPU在同一时刻,只能有一个进程,在执行多个进程时,中间会进行进程切换,只是时间很短,我们看不出来,以此来达到运行多个进程,即宏观并行,微观串行。如今的CPU都是多核,可以达到真正的并行操作,实现宏观并行,微观也是并行。

线程,又称轻量级进程(Light Weight Process)。 进程中的一条执行路径,也是CPU的基本调度单位。 一个进程由一个或多个线程组成,彼此间完成不同的工作, 同时执行,称为多线程。

 2、区别

1. 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。

2. 一个程序运行后至少有一个进程。

3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。

4. 进程间不能共享数据段地址,但是同进程的线程之间可以。

3、线程的组成 

任何一个线程都具有基本的组成部分            

1、CPU时间片: 操作系统(OS)会为每个线程分配执行时间。             

2、 运行数据:                

        堆空间: 存储线程需要的对象,多个线程可以共享堆中的数据。                

        栈空间:  存储线程需使用的局部变量,每个线程都拥有独立的栈。            

3、线程的逻辑代码.

4、线程的特点 

1、线程抢占式执行

        效率高

        可防止单一线程长时间独占CPU

2、在单核CPU中,宏观上同时执行,微观上顺序执行

 二、线程的创建

创建线程一共有三种方法

1、继承Thread类,并重写run()方法。

2、实现Runnable接口

3、实现Callable接口(主要与线程池一起使用)

1、 继承Thread类

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(i);
        }
    }
}

main方法

public class Test01 {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("主线程="+i);
        }
    }
}

2、实现Runnable

下面用一个买票的例子来实现这种方式。

四个窗口同时卖票,每个窗口都有100张票。

public class MyThread implements Runnable{
private Integer ticket = 100;
Lock l = new ReentrantLock();
    @Override
    public void run() {
        while (true){
                if (ticket>0){
                        ticket--;
                        System.out.println(Thread.currentThread().getName()+"卖了一张还剩:"+ticket+"张");
                }else {
                    break;
                }
        }
    }
}

测试:

public class Test01 {
    public static void main(String[] args) {
       MyThread myThread = new MyThread();
       Thread t1 = new Thread(myThread,"窗口A");
       Thread t2 = new Thread(myThread,"窗口B");
       Thread t3 = new Thread(myThread,"窗口C");
       Thread t4 = new Thread(myThread,"窗口D");
       t1.start();
       t2.start();
       t3.start();
       t4.start();
    }
}

 3、实现Callable接口

        这种方式主要与线程池一起配合使用,基础用法和Runnable相似,只是这种方法通过Thread来启动的话,太麻烦。他与Runnable接口唯一不同的就是,该接口中的方法有返回值和异常抛出。

public class My implements Callable<Integer> {
    private Integer sum=0;
    @Override
    public Integer call() throws Exception {
        for (int i=1;i<=100;i++){
            sum+=i;
        }
        return sum;
    }
}

 测试

public class Test {
    public static void main(String[] args) throws Exception{
        My m = new My();
        //自建创建线程对象并提交Callable类型的任务是比较麻烦的,需要封装到一个                    FutureTask类中,建议使用线程池来提交任务。
        //FutureTask futureTask = new FutureTask(m);
        //Thread t = new Thread(futureTask);
        //t1.start();
        //System.out.println(futureTask.get());
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<Integer> future = executorService.submit(m1);
        Integer integer = future.get();
        System.out.println(integer);
    }
}

4、获取线程ID和线程名称

1. 在Thread的子类中调用this.getId()或this.getName()

2. 使用Thread.currentThread().getId()和 Thread.currentThread().getName()

5、设置(修改)线程名称 

1. 调用线程对象的setName()方法

2. 使用线程子类的构造方法赋值

三、线程中常用的方法 

1、休眠

方法:sleep,是Thread类中的一个方法。

 public static void sleep(long millis)       使当前线程主动休眠,millis毫秒。

public class TakeSleep {
    public static void main(String[] args) {
        T t = new T();
        t.start();
        for (int i=0;i<20;i++){
            System.out.println("main==========="+i);
        }
    }
}
class T extends Thread{
    @Override
    public void run() {
        for (int i=0;i<20;i++){
            try {
                Thread.sleep(100);//是当前的线程进入一段休眠状态
                System.out.println("这是第"+i+"次");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
            }
        }
    }
}

 2、主动放弃

public static void yield()       当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

public class T extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            Thread.yield();//可能导致t1和t2的交替的频率变得高了。当前的线程主动放弃时间片,回到就绪状态,再和其他的竞争下一次的时间片
            System.out.println(Thread.currentThread().getName()+"第"+i+"次");
        }
    }
}
class TestYield {
    public static void main(String[] args) {
        T t1 = new T();
        t1.setName("三");
        T t2 = new T();
        t2.setName("四");
        t1.start();
        t2.start();
    }
}

 3、加入

public final void join()       加入线程

        当存在这个方法时,当前线程需要等待调用join()方法的线程执行完成之后,再执行当前线程。就像排队,中间有个插队的,你需要等待插队的那个买完之后,你才能买。一般用于主线程需要子线程的执行结果,或者前提操作时,用到join方法。

public class Test01 {
    public static void main(String[] args) throws Exception{
        ThreadJoin j = new ThreadJoin();
        j.setName("A");

        j.start();
        j.join();
        //把线程ThreadJoin加入当前的main线程中,直到ThreadJoin线程执行完毕后,main才会执行

        for (int i=0;i<20;i++){
            System.out.println("main==================="+i);
        }
    }
}
class ThreadJoin extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"这是第"+i+"次");
        }
    }
}

 4、守护线程

语法:线程对象.setDaemon(true);

        设置为守护线程。 线程有两类:用户线程(前台线程)和守护线程(后台线程)如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。垃圾回收线程属于守护线程。

public class Test01 {
    public static void main(String[] args) {
        ThreadDaemon t = new ThreadDaemon();
        t.setName("A");
        t.setDaemon(true);//设置ThreadDaemon为守护线程,当前台线程都执行完毕后,守护线程也会自动结束
        t.start();
        for (int i=0;i<10;i++){
            System.out.println("main==========="+i);
        }
    }
}
class ThreadDaemon extends Thread{
    @Override
    public void run() {
        for (int i=0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"第"+i+"次");
        }
    }
}

 四、线程安全、

1.什么是线程安全问题
        就是 多线程环境中 , 且存在数据共享 , 一个线程访问的共享 数据被其他线程修改了, 那么就发生了线程安全问题 , 整个访问过程中 , 无一共享的数据被其他线程修改了 就是线程安全的,程序中如果使用成员变量, 且对成员变量进行数据修改 , 就存在数据共享问题, 也就是线程安全问题。

2.为什么会有线程安全问题?
        当多个线程同时共享一个全局变量,或者静态变量, 进行写的操作时, 可能会发生数据的冲突问题 ,也就是线程安全问题, 但是做读的操作不会引发线程安全问题。例如我们在写多窗口卖同一个数量的票时,就会出现重复卖票和卖不存在的票等问题。

3.如和解决线程安全问题

  1. 使用同步机制, 使得在同一时间只能有一个线程修改共享数据。
  2. 消除共享数据, 即多个线程数据不共享或者共享的数据不被做修改 如果使用成员变量, 对成员变量不进行修改。 

线程状态: 

 

 1、同步方式

     同步代码块

语法:synchronized(临界资源对象){//对临界资源加锁

        //线程代码

}

注意:每个对象都有一个互斥锁标记,用来分配给线程的。 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块。线程退出同步代码块时,才会释放响应的互斥标记。

public class ThreadSaft {
    private static String[] array = new String[5];
    private static int index=0;

    public static void main(String[] args) throws Exception{
        Runnable hello = new Runnable() {
            @Override
            public void run() {
                synchronized (array) {
                    if (array[index] == null) {
                        array[index] = "啦啦啦啦啦";
                        index++;
                    }
                }
            }
        };
        Runnable world = new Runnable() {
            @Override
            public void run() {
                synchronized (array) {
                    if (array[index] == null) {
                        array[index] = "哈哈哈哈哈";
                        index++;
                    }
                }
            }
        };
        Thread t1 = new Thread(hello);
        Thread t2 = new Thread(world);
        t1.start();
        t2.start();
        t1.join();
        t2.join();//避免t1,t2还没有执行就执行主线程
        System.out.println(Arrays.toString(array));
    }
}

 2、同步方法

同步方法

语法:synchronized 返回值类型 方法名称(参数列表){

        //原子操作

}

同步规则:只有调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。如果不调用包含同步代码块的方法,或者普通方法时,则不需要锁标记,可直接调用。

已知JDK中线程安全的类:

        StringBuffer、Vector、Hashtable

        以上类中公开的方法,均为synchronized修饰的同步方法。

 3、手动锁(重入锁)

JDK5加入,与synchronized相比,显示定义,结构更加灵活

优势:提供更多使用性方法,功能更强大、性能更加优越。

语法:

Lock l = new ReentrantLock();//获取锁对象

l.lock();

try{

        //代码

}finally{

        l.unlock();

}

 五、线程死锁

为什么会出现线程死锁?

        当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,这时B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。

        死锁的原因:锁与锁之间有嵌套导致。

如何解决线程死锁?

1. 尽量减少锁得嵌套。
2. 可以使用一些安全类。
3. 可以使用Lock中得枷锁,设置枷锁时间。

public class Lock {
    public static Object a = new Object();
    public static Object b = new Object();
}

 

public class Boy extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (Lock.a){
            System.out.println(Thread.currentThread().getName()+"获取到A资源");
            synchronized (Lock.b){
                System.out.println(Thread.currentThread().getName()+"获取到B资源");
                System.out.println("完成");
            }
        }
    }
}
public class Girl extends Thread{
    @Override
    public void run() {
        synchronized (Lock.b){
            System.out.println(Thread.currentThread().getName()+"获取到A资源");
            synchronized (Lock.a){
                System.out.println(Thread.currentThread().getName()+"获取到B资源");
                System.out.println("完成");
            }
        }
    }
}

 测试:

public class Test {
    public static void main(String[] args) {
        Boy b = new Boy();
        Girl g = new Girl();
        b.setName("A线程");
        g.setName("B线程");
        b.start();
        g.start();
    }
}

六、线程通信

等待:释放锁,进入等待队列

        public final void wait()

        public final void wait(long timeout)

通知:唤醒等待队列中的线程,进入到就绪队列,参与CPU的竞争

        public final void notify();唤醒一个

        public final void notifyAll()唤醒所有

这些方法并不是Thread中的,而是Object类中的方法。

public class BankCard01 {
    private double balance;
    private boolean flag = false;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    public synchronized void save(double money){
        if (flag==true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //存钱
        this.balance = this.balance+money;
        System.out.println(Thread.currentThread().getName()+"存入了"+money+"现在卡里的金额为:"+this.balance);
        flag=true;
        this.notify();
    }
    public synchronized void task(double money){
        if (flag==false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //取钱
        this.balance = this.balance-money;
        System.out.println(Thread.currentThread().getName()+"取出了"+money+"现在卡里的金额为:"+this.balance);
        flag=false;
        this.notify();
    }
}

 

public class Test02 {
    public static void main(String[] args) {
        BankCard01 bankCard01 = new BankCard01();
        BoyTask b = new BoyTask(bankCard01);
        GirlTask g = new GirlTask(bankCard01);
        b.setName("king");
        g.setName("马闹鸡");
        b.start();
        g.start();
    }
}
//存钱
class BoyTask extends Thread{
    private BankCard01 bankCard01;
    public BoyTask(BankCard01 bankCard01){
        this.bankCard01 = bankCard01;
    }
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            bankCard01.save(1000);
        }
    }
}
//取钱
class GirlTask extends Thread{
    private BankCard01 bankCard01;
    public GirlTask(BankCard01 bankCard01){
        this.bankCard01 = bankCard01;
    }
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            bankCard01.task(1000);
        }
    }
}

 七、线程池

线程池类似与连接池,该池子中预先存储若干个线程对象,整个池子就是线程池。

作用:

        线程容器,可设定线程分配的数量上限

        将预先创建的线程对象存入池中,并重用线程池中的线程对象

        避免频繁的创建和销毁

线程池的创建:

        一共有四种线程池,根类为 Executor 类,ExecutorService:线程池的子接口。

 常用方法:

        shutdown():关闭线程池,需要等待线程池中任务执行完毕后才会关闭

        shutdownNow():立即关闭线程池

        isTerminated():判断线程池是否终止了

        submit():提交任务给线程池中线程对象、Runnable和Callable类型的任务

public class Test {
    public static void main(String[] args) {
        //创建单一线程池:适应场景:队列要求线程有序执行
        //ExecutorService executorService = Executors.newSingleThreadExecutor();
        //创建固定长度的线程池对象
        //ExecutorService executorService = Executors.newFixedThreadPool(3);
        //创建可变长度的线程池
        //ExecutorService executorService = Executors.newCachedThreadPool();
        //创建延迟线程池对象
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        for (int i=0;i<5;i++){
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~~~");
                }
            },3,TimeUnit.SECONDS);
        }
        executorService.shutdown();
    }
}

 在实际工作中不推荐使用 Executor 来创建线程池,而是使用原始的方法来创建线程池。

         int corePoolSize, 核心线程数
         int maximumPoolSize, 最大线程数
         long keepAliveTime, 空闲时间
         TimeUnit unit, 时间单位
         BlockingQueue<Runnable> workQueue 堵塞队列

public class Test {
    public static void main(String[] args) {
        BlockingQueue blockingQueue = new LinkedBlockingDeque(3);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,blockingQueue);
        for (int i=0;i<5;i++){
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"~~~~~");
                }
            });
        }
        threadPoolExecutor.shutdown();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值