java多线程--多线程基础小结

简介: 什么是线程?    在同一个进程中可以执行多个任务,每一个任务可以看做一个线程.   线程是程序的执行单元,执行路径,使程序使用cpu的最基本单位   一个进程如果只有一条执行路径,那么就是单线程的 一个进程如果有多个执行路径,那么就是多线程的 多线程的意义:    ...

什么是线程?

   在同一个进程中可以执行多个任务,每一个任务可以看做一个线程.

  线程是程序的执行单元,执行路径,使程序使用cpu的最基本单位

  一个进程如果只有一条执行路径,那么就是单线程的

一个进程如果有多个执行路径,那么就是多线程的

多线程的意义:

   多进程的存在是为了提高CPU的利用率,多线程的存在,不是为了提高程序的执行速度,而是为了提高应用程序的使用率.

   程序的执行其实都是在抢CPU的资源,即CPU的执行权.

   多个进程在抢这个资源,而其中一个进程如果有多个执行路径,即多个线程,那么就有更高的几率抢到CPU的执行权

JVM的启动是单线程的还是多线程的?

  是多线程的,启动jvm是就相当于启动了一个进程,接着该进程会创建一个主线程来调用main方法,而同时还有启动其他的线程,比如说垃圾回收的线程.

两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口.以第一种为例:

package com.wang.reflect;
class MyThread extends Thread{
    
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            //获取当前正在执行的线程的名称,相当于this.getName();
            System.out.println(Thread.currentThread().getName()+"::"+i);
        }
    }
}
public class TestReflect {
    public static void main(String[] args) throws Exception {
        MyThread mThread1=new MyThread();
        MyThread mThread2=new MyThread();
        //设置线程的名字,也可以直接通过带参构造函数设置线程名称
        mThread1.setName("张无忌:");
        mThread2.setName("赵敏:");
        mThread1.start();
        mThread2.start();
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"::"+i);
        }
    }
    
    
}

一般我们使用第二种方式实现多线程,实现Runnable接口的方式相比较继承Thread类的方式有两个好处:

    1.避免由于java单继承带来的局限性.

   2.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据,有效分离,较好地体现了面向对象的思想.

线程调度:

线程有两种调度模型:

    1.分式调度模型,所有的线程轮流使用CPU的使用权,平均分配每个线程占用的CPU时间片.

    2.抢占式调度模型,优先让优先级高的线程先执行,如果优先级相同,那么随机选择一个线程,优先级高的线程获得CPU的执行权的几率大一些,并不是严格按照优先级由高到低执行.

java使用的是抢占式调度模型.

方法介绍:

获取当前线程对象的优先级,默认优先级为5,范围是1~10 int  getPriority()
设置当前线程的优先级,范围1~10    void  setPriority()
在指定毫秒内让正在运行的线程休眠(暂停执行) static void sleep(1000)
----------------------------------------------------- -----------------------------------
等待当前线程执行结束,该方法一般紧随start()方法之后 final void join()
暂停当前正在执行的对象,执行其他线程,(礼让线程) static void yield()

将该线程标记为守护线程或用户线程。

当正在运行的线程都是守护线程时,Java 虚拟机退出。

该方法必须在启动线程前调用。

final void setDaemon(true)

看这张图,来更好的理解守护线程:

Image 2

假设现在有三个线程分别叫关羽,刘备,张飞.将关羽,张飞设置为守护线程,一旦刘备这个线程执行完毕(死掉),那么关羽,张飞也就没有活着的意义了(不再执行,退出).

中断线程,把线程状态终止,并抛出一个InterruptedException         void interrupt()

线程的生命周期:

Image 3

案例练习:

电影院卖票,假设有三个窗口同时卖票,共有100张票,用多线程模拟这个过程(要解决的关键问题是:线程同步).

package com.wang.reflect;
 
 
class SellTicket implements Runnable {
    // 共享变量
    private int tickets = 100;
    // 创建锁对象
    private Object obj = new Object();
 
 
    @Override
    public void run() {
        while (true) {
            /**
             * 解决线程同步问题的 方案:
             *     synchronized(锁对象){
             *        //操作共享变量的代码
             *  }
             * 
             */
            synchronized (obj) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票");
 
 
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            
        }
    }
}
 
 
public class SellTicketDemo {
 
 
    public static void main(String[] args) {
        SellTicket st = new 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();
 
 
    }
}

打印结果如下:

Image 4

synchronized是一个关键字.可以加在一段代码外部,也可以放在一个方法前.它的锁对象可以是任意对象,当然也可以写成当前类的对象this.

  • 同步代码块的锁对象:任意对象
  • 同步方法的锁对象:当前对象this
  • 同步静态方法的锁对象:当前类的字节码对象,类名.class;

 

java类库中线程安全的类有:StringBuffer,HashTable,Vector.

注意Vector是线程安全的,但是当我们需要一个线程安全的集合的时候,我们一般也不用它,在Collections类中获取线程安全的list集合的方法,假设我们现在需要获得一个线程安全的List<String>集合,可以这样写:

List<String> list=Collections.synchronizedList(new ArrayList<String>());

 

     虽然说我们可以理解同步代码块和同步方法的锁对象问题,但我们并没有直接看到在哪儿加上锁又在哪儿释放锁,为了更清晰的表达如何加锁和释放锁,JDK1.5以后提供了一个新的锁对象Lock.下面通过Lock锁实现上述卖票的过程.

package com.wang.reflect;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
class SellTicket implements Runnable {
    // 共享变量
    private int tickets = 100;
    // Lock是一个接口,ReentrantLock是他的一个实现类
    private Lock lock=new ReentrantLock();
 
    @Override
    public void run() {
        while (true) {
 
            lock.lock();//加锁
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第"
                        + (tickets--) + "张票");
 
            }
            lock.unlock();//释放锁
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
 
    }
}
 
public class SellTicketDemo {
 
    public static void main(String[] args) {
        SellTicket st = new 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();
 
    }
}

线程同步有两个很明显的弊:一是效率低,二是容易产生死锁.

死锁问题:

两个或两个以上的线程,在争夺资源的过程中,发生的一种相互等待的过程.下面通过代码演示死锁问题的存在:

package com.wang.reflect;
 
 
class DieLock extends Thread{
    private boolean flag;
    public DieLock(boolean flag){
        this.flag=flag;
    }
    public static final Object o1=new Object();
    public static final Object o2=new Object();
    @Override
    public void run() {
        if(flag){
            synchronized(o1){
                System.out.println("if o1");
                synchronized (o2) {
                    System.out.println("if o2");
                }
            }
            
        }else{
            synchronized (o2) {
                System.out.println("else o2");
                synchronized (o1) {
                    System.out.println("else o1");
                }
            }
        }
    }
}
public class DieLockDemo {
 
    public static void main(String[] args) {
        DieLock dl1=new DieLock(true);
        DieLock dl2=new DieLock(false);
        dl1.start();
        dl2.start();
    }
}

细读代码,可以发现,理想状态下,应该会打印:

if o1

if o2

else o2

else o1

但是运行之后,发现打印结果大多为:

if o1             或者     else o2

else o2                    if  o1

其实这里就发生了线程互相等待,也就是死锁问题.

生产者-消费者模式描述:

package com.wang.reflect;
 
//学生实体类作为共享资源
class Student{
    String name;//姓名
    int age;//年龄
    boolean flag;//标记变量,判断当前学生对象是否已创建赋值好
    
}
//模拟生产者线程类
class SetStudent implements Runnable{
 
    //共享资源s
    private Student s;
    private int x=0;
    
    public SetStudent(Student s) {
        this.s=s;
    }
    
    @Override
    public void run() {
        while(true){
            
            synchronized (s) {
                if(s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                
                if(x%2==0){
                s.name="郭靖";
                s.age=24;
                }else{
                    s.name="黄蓉";
                    s.age=18;
                }
                x++;
                //生产完之后,将标记置为true,通知消费者来消费
                s.flag=true;
                //唤醒线程
                s.notify();
            }
        }
    }
    
}
 
//模拟消费者线程类
class GetStudent implements Runnable{
 
        //共享资源s
        private Student s;
        
        
        public GetStudent(Student s) {
            this.s=s;
        }
    @Override
    public void run() {
        while(true){
            synchronized (s) {
                if(!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name+":::"+s.age);
                
                //消费完以后将标记之置于false,通知生产者来生产
                s.flag=false;
                //唤醒线程
                s.notify();
            }
        }
    }
}
//测试类
public class StudentDemo {
 
    public static void main(String[] args) {
        Student s =new Student();
        
        SetStudent ss=new SetStudent(s);
        GetStudent gs=new GetStudent(s);
        
        Thread t1=new Thread(ss, "生产者");
        Thread t2=new Thread(gs, "消费者");
        
        t1.start();
        t2.start();
    }
}

在上面的代码中,生产者就是代表对Student属性进行赋值,消费者就是代表对Studnet属性进行打印,代码设置了等待唤醒机制.

上述代码可以优化的,可以讲生产者的赋值行为,和消费者的打印行为,封装到Student类中,写成两个同步方法.实现这两个功能.代码如下:

package com.wang.reflect;
 
//学生实体类作为共享资源
class Student {
    private String name;// 姓名
    private int age;// 年龄
    boolean flag;// 标记变量,判断当前学生对象是否已创建赋值好
 
    public synchronized void set(String name, int age) {
 
        if (this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name;
        this.age = age;
 
        this.flag = true;
        this.notify();
    }
 
    public synchronized void get() {
        if (!this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
 
        System.out.println(name + ":::" + age);
 
        this.flag = false;
        this.notify();
    }
 
}
 
// 模拟生产者线程类
class SetStudent implements Runnable {
 
    // 共享资源s
    private Student s;
    private int x = 0;
 
    public SetStudent(Student s) {
        this.s = s;
    }
 
    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                s.set("郭靖", 27);
            } else {
                s.set("黄蓉", 18);
            }
            x++;
        }
    }
 
}
 
// 模拟消费者线程类
class GetStudent implements Runnable {
 
    // 共享资源s
    private Student s;
 
    public GetStudent(Student s) {
        this.s = s;
    }
 
    @Override
    public void run() {
        while (true) {
            s.get();
        }
    }
 
}
 
// 测试类
public class StudentDemo {
 
    public static void main(String[] args) {
        Student s = new Student();
 
        SetStudent ss = new SetStudent(s);
        GetStudent gs = new GetStudent(s);
 
        Thread t1 = new Thread(ss, "生产者");
        Thread t2 = new Thread(gs, "消费者");
 
        t1.start();
        t2.start();
    }
 
}
相关文章
|
2天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
2天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
2天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
12 3
|
2天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
19 2
|
2天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
12 1
|
10天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
40 6
|
19天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
25天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
23天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
25天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####