Heim > Java > Java-Tutorial > Hauptteil

Java经典技巧之实现多线程、线程同步

WBOY
Freigeben: 2022-04-26 18:05:36
nach vorne
2677 人浏览过

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于核心类库中多线程和线程同步的相关问题,下面就一起来看一下,希望对大家有帮助。

Java经典技巧之实现多线程、线程同步

推荐学习:《java视频教程

1 多线程

1.1 进程

  • 进程:是正在运行的程序
    • 是系统进行资源分配和调用的独立单位
    • 每一个进行都有它自己的内存空间和系统资源
  • 进程的三个特征  
    • 独立性:进程与进程之间是相互独立的,彼此有自己独立内存区域
    • 动态性:进程是运行中的程序,要动态的占用内存,CPU和网络等资源
    • 并发性:CPU会分时轮询切换依次为每个进程服务,因为切换的速度非常快,给我们的感觉像是在同时执行,这就是并发性(并发:同一时刻同时有多个在执行)

1.2 线程

  • 线程:是进程中的单个顺序控制流,是一条执行路径
    • 单线程:一个进程只有一条执行路径
    • 多线程:一个进程有多条执行路径

1.3 多线程的实现方式

1.3.1 方式1:继承Tread类

  • 流程

    • 1、定义一个MyTread类继承Tread类
    • 2、在MyTread类中重写run()方法
    • 3、创建MyTread类的对象
    • 4、启动线程:void start()
  • 为什么要重写run()方法?

    • 因为run()是用来封装被线程执行的代码
  • run()方法和start()方法的区别?

    • run():封装线程执行的代码,直接调用,相当于普通方法的的调用
    • start():启动线程,然后由JVM调用此线程中的run()方法
  • 范例

  • MyTread类:

package test;//1、定义一类MyTread继承Tread类public class MyThread extends Thread{
    2、在MyTread类中重写run()方法
    @Override
    public void run() {
        for(int i=0;i<100;i++) {
            System.out.println(i);
        }
    }}
Nach dem Login kopieren
  • 测试类
package test;public class Demo {
    public static void main(String[] args) {
        //3、创建MyTread类的对象
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //4、启动线程:void start():启动线程,由Java虚拟机调用此线程的run()方法
        my1.start();
        my2.start();
    }}
Nach dem Login kopieren

1.3.2 方式2:实现Runnable接口

  • 流程
    • 1、定义一个MyRunnable类实现Runnable接口
    • 2、在MyRunnable类中重写run()方法
    • 3、创建MyRunnable类的对象
    • 4、创建Tread类的对象,把MyRunnable对象作为构造方法的参数
    • 5、启动线程
  • 好处:
    • 避免了Java单继承的局限性
    • 适合多个相同程序的代码取处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好地体现了面向对象的设计理论
package test;public class Demo {
    public static void main(String[] args) {
        //3、创建MyRunnable类的对象
        MyRunnable mr = new MyRunnable();

        //4、创建Tread类的对象,把MyRunnable对象作为构造方法的参数//        Thread t1 = new Thread(mr);//        Thread t2 = new Thread(mr);
        //Thread(Runnable target,String name)
        Thread t1 = new Thread(mr,"高铁");
        Thread t2 = new Thread(mr,"飞机");

        //5、启动线程
        t1.start();
        t2.start();
    }}
Nach dem Login kopieren

1.3.3 方式3:实现Callable接口

1.4 设置和获取线程名称

  • Thread类中设置和获取线程名称的方法
方法名说明
void setName(Stringname)将此线程的名称更改为等于参数name
String getName()返回此线程的名称
public Thread(String name)通过构造方法也可以设置线程名称
public static Thread currentThread()返回对当前正在执行的线程对象的引用(可以返回main()方法中线程)
public static void sleep(long time)让当前线程休眠多少毫秒再继续执行
  • MyThread类
package test;public class MyThread extends Thread{
    //构造方法添加线程名称
    public MyThread(){}
    public MyThread(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        for(int i=0;i<100;i++) {
            //1,String getName() 	返回此线程的名称
            System.out.println(getName()+":"+i);
        }
    }}
Nach dem Login kopieren
  • 测试类
package test;public class Demo {
    public static void main(String[] args) {
        /*        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //2,void setName(Stringname) 	将此线程的名称更改为等于参数name
        my1.setName("高铁");
        my2.setName("飞机");*/
        
        //3,通过构造方法设置线程名称
        //需要自己定义的类中提供此带参构造方法,并通过super访问父类带参构造方法
        /*MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机"); 

        my1.start();
        my2.start();*/
        
        //4,public static Thread currentThread() 	返回对当前正在执行的线程对象的引用(可以返回main()方法中线程)
        System.out.println(Tread.currentThread().getName()); //main
    }}
Nach dem Login kopieren

1.5 线程调度

  • 线程有两种调度模型

    • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
    • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
  • Java使用的是抢占式调度模型

  • 假如计算机只有一个CPU, 那么CPU在某一个时刻只能执行条指令, 线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

  • Thread类中设置和获取线程优先级的方法

方法名说明
public final int getPriority() [praɪˈɔːrəti]返回此线程的优先级
public final void setPriority(int newPriority)更改此线程的优先级
  • 线程默认优先级是5;线程优先级范围是:1-10
  • 线程优先级高仅仅表示线程获取的CPU时间的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
package test;public class Demo {
    public static void main(String[] args) {

        ThreadPriority tp1 = new ThreadPriority();
        ThreadPriority tp2 = new ThreadPriority();
        ThreadPriority tp3 = new ThreadPriority();


        tp1.setName("高铁");
        tp2.setName("飞机");
        tp3.setName("汽车");

        //1,public final int getPriority() [praɪˈɔːrəti] 	返回此线程的优先级//        System.out.println(tp1.getPriority()); //5//        System.out.println(tp2.getPriority()); //5//        System.out.println(tp3.getPriority()); //5

        //2,public final void setPriority(int newPriority) 	更改此线程的优先级
        System.out.println(Thread.MAX_PRIORITY); //10
        System.out.println(Thread.MIN_PRIORITY); //1
        System.out.println(Thread.NORM_PRIORITY); //5

        //设置正确优先级
        tp1.setPriority(5);
        tp2.setPriority(10);
        tp3.setPriority(1);

        tp1.start();
        tp2.start();
        tp3.start();
    }}
Nach dem Login kopieren

1.6 线程控制

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join()等待这个线程死亡
void setDaemon(boolean on) [ˈdiːmən]将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机很快将退出 (并不是立刻退出)

案例:sleep()方法

  • 线程类
package test;public class ThreadSleep extends Thread{
    @Override
    public void run() {
        for(int i=0;i<10;i++) {
            System.out.println(getName()+":"+i);
            //1,static void sleep(long millis) 	使当前正在执行的线程停留(暂停执行)指定的毫秒数
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }}
Nach dem Login kopieren
  • 测试类
package test;public class Demo {
    public static void main(String[] args) {

        ThreadSleep ts1 = new ThreadSleep();
        ThreadSleep ts2 = new ThreadSleep();
        ThreadSleep ts3 = new ThreadSleep();


        ts1.setName("曹操");
        ts2.setName("刘备");
        ts3.setName("孙权");


        ts1.start();
        ts2.start();
        ts3.start();//        曹操:0//        孙权:0//        刘备:0//        孙权:1//        曹操:1//        刘备:1//        ...
    }}
Nach dem Login kopieren

案例:join()方法

package test;public class Demo {
    public static void main(String[] args) {

        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj1.setName("康熙");
        tj2.setName("四阿哥");
        tj3.setName("八阿哥");

        tj1.start();
        //2,void join() 	等待这个线程死亡
        try {
            tj1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tj2.start();
        tj3.start();//        康熙:0//        康熙:1//        康熙:2//        四阿哥:0//        四阿哥:1//        八阿哥:0//        八阿哥:1//        八阿哥:2//        四阿哥:2//        ...
    }}
Nach dem Login kopieren

案例:setDaemon()方法

package test;public class Demo {
    public static void main(String[] args) {

        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj2.setName("关羽");
        tj3.setName("张飞");
        //设置主线程为刘备
        Thread.currentThread().setName("刘备");

        //3,void setDaemon(boolean on) 	将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
        tj1.setDaemon(true);
        tj2.setDaemon(true);

        tj1.start();
        tj2.start();

        for(int i=0;i<2;i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
        //刘备执行完后,关羽和张飞会很快结束
    }}
Nach dem Login kopieren

1.7 线程生命周期

在这里插入图片描述

1.8 数据安全问题之案例:买票

在这里插入图片描述
在这里插入图片描述

  • 为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据
  • 如何解决多线程安全问题呢?

  • 基本思想:让程序没有安全问题的环境

  • 怎么实现呢?

    • 把多条语句操作共享 数据的代码给锁起来,让任意时刻只能有一一个线程执行即可
    • Java提供 了同步代码块的方式来解决

1.9 线程同步_同步代码块

  • 锁多条语句操作共享数据,可以使用同步代码块实现
  • 格式
synchronized(任意对象) {
	多条语句操作共享数据的代码}
Nach dem Login kopieren
  • 好处:让多个线程实现先后依次访问共享资源,解决了多线程的数据安全问题

  • 弊端:当线程很多的时候,因为每个线程都会去判断同步上的锁,这是很消耗资源的,无形中降低程序的运行效率

  • sellTicket类

package test;//1,定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets= 100;public class SellTicket implements Runnable{
    private int tickets = 100;
    private Object obj = new Object();

    //2,在ellTicket类中重写run0方法实现卖票, 代码步骤如下
    @Override
    public void run() {
        while(true) {
            //tickes=100
            //t1,t2,t3
            //假设t1抢到CPU执行器
            synchronized (obj){
                //t1进来后把代码锁起来了
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets=99
                }
                //t1出来了,锁就被释放了
            }
        }
    }}
Nach dem Login kopieren
  • 测试类
package test;public class SellTicketDemo {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();

    }}
Nach dem Login kopieren

1.10 线程同步_同步方法

  • 作用:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待
  • 同步方法:就是把synchronized关键字加到方法上;锁对象为:this  
    • 格式:修饰符 synchronized 返回值类型 方法名(方法参数) {}
  • 同步静态方法:就是把synchronized关键字加到静态方法上面;锁对象为:类名.class  
    • 格式:修饰符 static synchronized 返回值类型 方法名(方法参数) {}
package test;public class SellTicket implements Runnable{//1非静态    private int tickets = 100;
    private static int tickets = 100;
    private Object obj = new Object();
    private int x = 0;

    @Override
    public void run() {
        while(true) {
            if(x%2==0) {//1非静态                synchronized (this) {
                synchronized (SellTicket.class) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--; //tickets=99
                    }
                }
            } else {//                synchronized (obj) {//                    if (tickets > 0) {//                        try {//                            Thread.sleep(100);//                        } catch (InterruptedException e) {//                            e.printStackTrace();//                        }//                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");//                        tickets--; //tickets=99//                    }//                }
                sellTicket();
            }
            x++;
        }
    }//1非静态//    private synchronized void sellTicket() {//        if (tickets > 0) {//            try {//                Thread.sleep(100);//            } catch (InterruptedException e) {//                e.printStackTrace();//            }//            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");//            tickets--; //tickets=99//        }//    }

    private static synchronized void sellTicket() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
            tickets--; //tickets=99
        }
    }}
Nach dem Login kopieren

1.11 线程安全的类(了解)

源码中方法都被synchronized修饰

StringBuffer

  • 线程安全, 可变的字符序列
  • 从版本JDK 5开始,被StringBuilder替代。通常应该使用StringBuilder类, 因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector

  • 从Java 2平台v1.2开始,该类改进了List接口, 使其成为Java Collections Framework的成员。 与新的集合实现不同,Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector

Hashtable

  • 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值
  • 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable

Collections类中static List snchronizedList(List list):返回由指定列表支持的同步(线程安全)的列表

package test;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;public class Demo {
    public static void main(String[] args)  {
        //static  List snchronizedList(List list):返回由指定列表支持的同步(线程安全)的列表
        Collection list = Collections.synchronizedList(new ArrayList());

        /*源码都是返回Synchronized
        public static  List synchronizedList(List list) {
            return (list instanceof RandomAccess ?
                    new Collections.SynchronizedRandomAccessList<>(list) :
                    new Collections.SynchronizedList<>(list));
        }*/

    }}
Nach dem Login kopieren

1.12 Lock锁

  • Lock是接口不能直接实例化,采用实现类ReentrantLock来实例化(JDK5以后)
  • ReentrantLock构造方法:
方法名 说明
ReentrantLock() 创建一个ReentrantLock的实例对象
  • Lock中获得锁和释放锁方法:
方法名 说明
void lock() 获得锁
void unlock() 释放锁
  • 推荐使用try{} finall{}代码块来加锁和释放锁
package test;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class SellTicket implements Runnable{
    private static int tickets = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }finally {
                lock.unlock();
            }
        }
    }}
Nach dem Login kopieren

1.13 线程通讯

  • 线程通信一定是多个线程在操作同一个资源才需要通信
方法名 说明
public void wait() 让当前线程进入到等待状态,此方法必须锁对象调用
public void notify() 唤醒当前锁对象上等待状态的某个线程,此方法必须锁对象调用
public void notifyAll() 唤醒当前锁对象上等待状态的全部线程,此方法必须锁对象调用

1.14 生产者消费者

1.14.1 生产者消费者概述

在这里插入图片描述

  • 为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中
  • Object类的等待和唤醒方法
方法名 说明
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

1.14.2 生产者消费者案例

在这里插入图片描述

  • 奶箱类
package test;//1:定义奶箱类public class Box {
    //定义一个成员变量,表示第x瓶奶
    private int milk;
    //定义一个成员变量表示奶箱的状态
    private boolean state = false;

    //提供存储牛奶和获取牛奶的操作
    public  synchronized void put(int milk) {
        //如果有牛奶等待消费
        if(state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没有牛奶,就生产牛奶
        this.milk = milk;
        System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");

        //生产完毕后,修改奶箱状态
        state = true;

        //唤醒其他等待线程
        notifyAll();
    }

    public  synchronized void get() {
        //如果没有牛奶,就等到生产
        if(!state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果有牛奶,就消费牛奶
        System.out.println("用户拿到第" + this.milk + "瓶奶");

        //消费完毕后,修改奶箱状态
        state = false;

        //唤醒其他等待线程
        notifyAll();
    }}
Nach dem Login kopieren
  • 生产者类
package test;//2:生产者类(Producer):实现Runnable接口public class Producer implements Runnable {
    private Box b;

    public Producer(Box b) {
        this.b = b;
    }

    //重写run()方法,调用存储牛奶的操作
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            b.put(i);
        }
    }}
Nach dem Login kopieren
  • 消费者类
package test;//3:消费者类(Customer);实现Runnable接口public class Customer implements Runnable{
    private Box b;

    public Customer(Box b) {
        this.b = b;
    }

    //重写run()方法,调用获取牛奶的操作
    @Override
    public void run() {
        while(true) {
            b.get();
        }
    }}
Nach dem Login kopieren
  • 测试类
package test;public class BoxDemo {
    public static void main(String[] args) {
        //创建奶箱对象,这是共享数据区域
        Box b = new Box();

        //创建生产者对象,把奶箱对象作为构造方法参数传递。因为在这个类中要谓用存储牛奶的操作
        Producer p = new Producer(b);

        //创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
        Customer c  =new Customer(b);

        //创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);

        //启动线程
        t1.start();
        t2.start();//        送奶工将第1瓶奶放入奶箱//                用户拿到第1瓶奶//        送奶工将第2瓶奶放入奶箱//                用户拿到第2瓶奶//        送奶工将第3瓶奶放入奶箱//                用户拿到第3瓶奶//        送奶工将第4瓶奶放入奶箱//                用户拿到第4瓶奶//        送奶工将第5瓶奶放入奶箱//                用户拿到第5瓶奶
    }}
Nach dem Login kopieren

推荐学习:《java视频教程

以上是Java经典技巧之实现多线程、线程同步的详细内容。更多信息请关注PHP中文网其他相关文章!

Verwandte Etiketten:
Quelle:csdn.net
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!