栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JavaSE---多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JavaSE---多线程

多线程的概念

多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行 执行的线程来完成各自的任务。

何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、 网络操作、搜索等。
需要一些后台运行的程序时。
多线程的优点
提高程序的响应.
提高CPU的利用率.
改善程序结构,将复杂任务分为多个线程,独立运行.
多线程的缺点
对内存消耗高:线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
对CPU要求高:多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程安全问题:线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
单一的多线程不会出现安全问题,每个线程都在做自己的事情,没有交集。

线程同步

为了解决线程安全问题,同步=排队+锁;

并发与并行

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:在一个时间段内依次执行操作.例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行

多线程同步

多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制, 即各线程间要有先来后到;

同步就是排队+锁:

几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作;
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制

同步锁

同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用 来充当锁标记).
同步执行过程
1.第一个线程访问,锁定同步对象,执行其中代码.
2.第二个线程访问,发现同步对象被锁定,无法访问.
3.第一个线程访问完毕,解锁同步对象.
4.第二个线程访问,发现同步对象没有锁,然后锁定并访问.

一个线程持有锁会导致其他所有需要此锁的线程挂起;在多线程竞争下, 加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题.

案例:模拟卖票 两个窗口分别售票,票数为10张
分别使用继承Thread和实现Runnable两种方式实现
没加锁时,直接共享资源,出现了线程安全问题

确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个 线程获取了这把锁,才有权利访问该共享资源。

继承Thread

使用synchronized(同步锁)关键字同步方法或代码块。

 		static  int num = 10;//假设有10张票   static变量只有一份,多个对象共享
        static Object obj = new Object();//多个线程共享的同一个锁对象

  //模拟出票
        @Override
        public void run() {
            while(true){
          synchronized(obj){
                    if(num>0){
                        System.out.println(Thread.currentThread().getName()+":"+num);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        num--;
                    }else{
                        System.out.println("票卖完啦");
                        break;
                    }
                }
            }
        }

synchronized还可以放在方法声明中,表示整个方法,为同步方法。
synchronized修饰非静态方法时,锁对象默认是this
synchronized修饰的是static方法,那么锁对象是类的Class对象.
每一个类被加载到内存时,就会为类创建一个Class类的对象,无论创建了多少个对象,Class对象只有一个

  //模拟出票
        @Override
        public void run() {
            while(true){
                    this.print();
                    if(num==0){
                        System.out.println("票卖完啦");
                        break;
                    }

            }
        }
        public static synchronized  void print(){
            if(num>0){
                System.out.println(Thread.currentThread().getName()+":"+num);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
            }
        }

实现Runnable

synchronized (this)此时就可以用this,这里只有一个对象

 int num = 10;//这就是共享资源
    @Override
    public void run() {
        while (true){
            synchronized (this){
                if(num>0){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num--;
                }else{
                    System.out.println("票卖完了");
                    break;
                }
            }
        }
    }

public synchronized void print()就不用再定义为static

public class TicketThread2 implements Runnable{
    
    int num = 10;//这就是共享资源
    @Override
    public void run() {
        while(true){
            this.print();
            if(num==0){
                System.out.println("票卖完啦");
                break;
            }

        }
    }
    
    public  synchronized  void print(){
        if(num>0){
            System.out.println(Thread.currentThread().getName()+":"+num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
        }
    }
}
synchronized关键字

synchronized加锁实现,靠底层指令控制实现
synchronized可以修饰代码块和方法,注意锁的对象(锁对象可能会变)
1-修饰代码块
synchronized (同步锁){
// 需要被同步的代码;
}
synchronized(锁对象) 锁对象可以是任何对象,但是必须是唯一的,也就是多个线程对应的是同一个锁对象.
在对象中,有一个区域对叫对象头,象头中有一个锁的标志为,无锁和已被使用
2- synchronized修饰方法
synchronized还可以放在方法声明中,表示整个方法,为同步方法。
synchronized修饰非静态方法时,锁对象默认是this
synchronized修饰的是static方法,那么锁对象是类的Class对象.
每一个类被加载到内存时,就会为类创建一个Class类的对象,无论创建了多少个对象,Class对象只有一个

synchronized加锁方式是隐式的,进入到同步代码块,自动获取锁,同步代码块执行完成后,自动释放锁

Lock(锁)

从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象 来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存 语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加 锁、释放锁.
Lock是靠java代码来控制的
Lock只能在某一段代码加锁,而且是显式的加锁和释放锁
注意:最好写在

try{
代码块
}finally{
lock.unlock();
}

里面,一旦出现异常,也保证把锁释放掉,以免出现死锁

package day16.Class;

import java.util.concurrent.locks.ReentrantLock;

public class TicketThread3 implements Runnable{
    
    int num = 10;//这就是共享资源
    ReentrantLock lock = new ReentrantLock();
    //模拟出票
    @Override
    public void run() {
        while(true){
           try{
               lock.lock();//加锁
               if(num>0){
                   System.out.println(Thread.currentThread().getName()+":"+num);
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   num--;
               }else{
                   System.out.println("票卖完啦");
                   break;
               }

           }finally {
               lock.unlock();//释放锁
           }
        }
    }
}

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步 资源,就形成了线程的死锁.
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续.
锁嵌套时容易发生死锁现象

package day16.Class;

public class DieThread extends Thread{
    boolean flag;

    public DieThread(boolean flag) {
        this.flag = flag;
    }

    static  Object objA = new Object();
    static  Object objB = new Object();
    @Override
    public void run() {
        if(flag){
            synchronized (objA){
                System.out.println("if objA");
                synchronized (objB){
                    System.out.println("if objB");
                }
            }
        }else{
            synchronized (objB){
                System.out.println("else if objB");
                synchronized (objA) {
                    System.out.println("else if objA");
                }
            }

        }
    }
}

线程通信

线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。
涉及三个方法:
wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait, 就唤醒优先级高的那个。
notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
注意: .wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
wait()和sleep()
相同:让线程进入阻塞状态
不同:sleep()是Thread类的方法,不会释放锁,休眠时间到了后,会自动进入就绪状态
wait()是Object类的方法,会释放锁。wait后的线程,需要使用notify或notifyAll来唤醒
案例:两个线程交替打印1-100之间的数字

package day16.Class;

public class PrintThread implements Runnable{
    int num = 0;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                this.notify();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


                if (num <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    num++;
                } else {
                    break;
                }
                try {
                    this.wait();//让线程等待,一定要调用的是锁对象的wait()
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

经典例题:生产者/消费者问题

生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来 取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待.

package day16.Class.PCquestion;
//生产者
public class Productor extends Thread{
    Counter counter;

    public Productor(Counter c) {
        this.counter = c;
    }

    @Override
    public void run() {
        counter.jia();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package day16.Class.PCquestion;
//消费者
public class Customer extends Thread{
    Counter counter;

    public Customer(Counter c) {
        this.counter = c;
    }

    @Override
    public void run() {
        while (true){
            counter.jian();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package day16.Class.PCquestion;
//柜台
public class Counter {
    int num = 1;
    //生产者调用
    //synchronized修饰的是非静态方法,锁对象默认是this,Counter c = new Counter();
    public synchronized void jia(){
        if(num == 0){
            num++;
            System.out.println("生产者生产了一个商品");
            this.notify();
        }else {
            try {
                this.wait();//生产者等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费者调用
    public synchronized void jian(){
        if(num==1){
            num--;
            System.out.println("消费者取走了一个商品");
            this.notify();
        }else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package day16.Class.PCquestion;

public class Test {
    public static void main(String[] args) {
        Counter c = new Counter();
        Productor p = new Productor(c);
        p.start();//启动生产线程
        Customer co = new Customer(c);
        co.start();//启动消费线程
    }
}

新增创建线程方式Callable接口

继承Thread和实现Runnable 最终都是重写run()
run()没有返回值,也不能抛出异常,存在局限性
java推出Callable接口,里面定义了call()
实现Callable接口与使用Runnable相比,Callable功能更强大些.
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,获取返回结果
接收任务
FutureTask futureTask = new FutureTask(任务);
创建线程
Thread t = new Thread(futureTask);
t.start();
Integer val = futureTask.get();获得线程call方法的返回值

package day16.Class.NewThread;

import java.util.concurrent.Callable;

public class SumThread implements Callable {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        Thread.sleep(100);
        for (int i = 0; i <=10; i++) {
            sum+=i;
        }
        return sum;
    }
}

package day16.Class.NewThread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) {
        SumThread sumThread = new SumThread();
        FutureTask futureTask = new FutureTask(sumThread);
        Thread t = new Thread(futureTask);
        t.start();
        try {
            Integer sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/737417.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号