Java多线程编程详解——学习笔记

admin 08-22 系统学习 暂无评论
关于线程的一些概念、基础知识
 

线程相关的概念


并发和并行的区别
 
并行:在同一时刻,有多个指令在多个CPU上同时执行。
 
并发:在同一时刻,有多个指令在单个CPU上交替执行。

形象的解释就是:并行就是三个厨师同时抄了3道菜;并发就是一个厨师同时抄了3道菜;


进程和线程的区别

进程:就是操作系统中正在运行的一个应用程序。
       
 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
 
         动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
 
         并发性:任何进程都可以同其他进程一起并发执行
 
线程:就是应用程序中做的事情,比如: 360软件中的杀毒,扫描木马,清理垃圾。

          在操作系统中,线程是比进程更小的能够独立运行的基本单位。同时,它也是CPU调度的基本单位。线程本身基本上不拥有系统资源,只是拥有一些在运行时需要用到的系统资源,例如程序计数器,寄存器和栈等。一个进程中的所有线程可以共享进程中的所有资源。


多线程:

多线程可以理解为在同一个程序中能够同时运行多个不同的线程来执行不同的任务,这些线程可以同时利用CPU的多个核心运行。多线程编程能够最大限度的利用CPU的资源。


并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序最大限度地并发执行。

cpu时间片

即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。

 

创建线程的3种方式

  • 继承Thread类的方式进行实现
     
  • 实现Runnable接口的方式进行实现
     
  • 利用Callable和Future接口方式实现


1、继承Thread类(重点)

 
package com.shichun.study.thread;

/**
 * 创建线程的3中方法:第一种:继承Thread类型   重写run方法  调用start()方法
 *
 * @author chunge
 * @description: TODO
 * @date 2021/8/2214:07
 */
public class TestThread1 extends Thread {

    @Override
    public void run() {
        //run 线程体
        for (int a = 0; a < 100; a++) {
            System.out.println("我是线程,正在执行:" + a);
        }
    }

    public static void main(String[] args) {
        //创建线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start方法
        testThread1.start();

        for (int a = 0; a < 100; a++) {
            System.out.println("我是主线程,正在执行:" + a);
        }


    }

}


2、实现Runnable接口(重点)
 
package com.shichun.study.thread;

/**
 * @author chunge
 * @description: TODO
 * @date 2021/8/2218:04
 */
public class TestThread2 implements Runnable{


    @Override
    public void run() {
        //run 线程体
        for (int a = 0; a < 100; a++) {
            System.out.println("我是线程,正在执行:" + a);
        }
    }


    public static void main(String[] args) {
        //创建线程对象
        TestThread2 testThread2 = new TestThread2();
        //调用start方法
        new Thread(testThread2).start();

        for (int a = 0; a < 100; a++) {
            System.out.println("--------------我是主线程,正在执行:" + a);
        }

    }

}



3、Callable 和 Future 接口来创建线程(了解)

      第一步:创建一个 MyCallable 类来实现 Callable接口

 
package com.chunge.thread;

import java.util.concurrent.Callable;

/**
 * @author 荷兰男孩
 * @description: TODO
 * @date 2021/9/1520:07
 */
public class MyCallable implements Callable<String> {

    /**
     * 这个方法体里面就是线程要执行的内容
     * 这里需要注意方法有一个返回值
     * @return
     * @throws Exception
     */
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 1000; i++) {
            System.out.println("我正在报数:");
        }
        return "报数完毕";
    }
}

      第二步:通过FutureTask和Thread启动线程
 
package com.chunge.thread;

import com.chunge.benpost.MyBeanPostProcessor;

import java.util.concurrent.FutureTask;

/**
 * @author 荷兰男孩
 * @description: TODO
 * @date 2021/9/1520:10
 */
public class Test {

    public static void main(String[] args) {
        //创建MyCallable对象
        MyCallable myCallable=new MyCallable();
        //包装一层,这里可以获取到线程执行的返回直结果
        FutureTask<String> future= new FutureTask<String>(myCallable);
        //创建一个Thread对象来启动线程
        Thread thread= new Thread(future);
        //启动
        thread.start();
    }
}



3种创建线程方式的比较:

 

  • 实现Runnable、Callable接口

    • 好处: 扩展性强,实现该接口的同时还可以继承其他的类

    • 缺点: 编程相对复杂,不能直接使用Thread类中的方法

  • 继承Thread类

    • 好处: 编程比较简单,可以直接使用Thread类中的方法

    • 缺点: 可以扩展性较差,不能再继承其他的类

线程类的常用方法



获取和设置线程名称

  (1)获取线程名称   
   
          String getName() :返回此线程的名称

  (2)设置线程名称

         void setName(String name)  ;如果不设置名称,线程是有默认名字的,格式为:Thread-编号  。

        还可以通过构造函数设置线程名称,比如   Thread  t= new Thread(“线程名称”);


获取当前线程的对象

     public static native Thread currentThread() 返回当前正在执行线程对象的引用

     使用方法:
 Thread.currentThread().getName();
让线程休眠
    public static void sleep(long millis) 让线程休眠指定的时间  单位:毫秒

    使用方法:

 Thread.sleep(1000);//让线程休眠1秒钟

      






线程的优先级

   优先级:  1-10   数值越大,优先级越高

   设置线程的优先级    public final void setPriority(int newPriority)

   获取线程的优先级  public final int getPriority()





设置为守护线程

什么是守护线程?  

设置:  public final void setDaemon(boolean on)    守护线程   设置为true 

 

线程的生命周期


一个线程从创建,到最终的消亡,需要经历多种不同的状态,而这些不同的线程状态,由始至终也构成了线程生命周期的不同阶段。线程的生命周期可以总结为下图。






其中,几个重要的状态如下所示。

NEW:初始状态,线程被构建,但是还没有调用start()方法。

RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。

BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。

WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。

TIME_WAITING:超时等待状态。可以在一定的时间自行返回。

TERMINATED:终止状态,当前线程执行完毕。


 

线程的安全问题:


案例:卖票问题,
 
public class SellTicket implements Runnable {
    private int tickets = 100;
    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
        while (true) {
            if(ticket <= 0){
                    //卖完了
                    break;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
        }
    }
}
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();
    }
}
  • 卖票出现了问题

    • 相同的票出现了多次

    • 出现了负数的票

  • 问题产生原因

    线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题



volatile

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。

可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值

Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。



synchronized 同步锁机制


Java中的每一个对象都可以作为锁

·对于普通同步方法,锁是当前实例对象。

·对于静态同步方法,锁是当前类的Class对象。

·对于同步方法块,锁是Synchonized括号里配置的对象。

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。



 

死锁问题:

什么事死锁:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
 

什么情况下会产生死锁:

(1)资源有限

(2)同步嵌套
 

代码演示:
 
public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while(true){
                synchronized (objA){
                    //线程一
                    synchronized (objB){
                        System.out.println("小康同学正在走路");
                    }
                }
            }
        }).start();

        new Thread(()->{
            while(true){
                synchronized (objB){
                    //线程二
                    synchronized (objA){
                        System.out.println("小薇同学正在走路");
                    }
                }
            }
        }).start();
    }
}


生产者消费者模式:







阻塞队列




 

转载请注明来自一个开发者的工作笔记——荷兰男孩的博客,本文标题:《Java多线程编程详解——学习笔记》

喜欢( ) 发布评论

分享到:

Java高并发编程——jvm内存模型 rabbitMq消息队列学习笔记