IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java多线程入门 -> 正文阅读

[Java知识库]Java多线程入门

作者:token comment

线程,进程,程序的区别

程序(program):是为完成特定任务,某种语言编写的一组指令的集合.即指一段静态的代码,静态对象.
程序(process):是程序的一次执行过程,或是正在运行的一个程序.是一个动态的过程,由它自身的产生,存在,消亡的过程----生命周期.

运行的某个程序可以是一个进程,比如浏览器
程序是静态的,进程是动态的
进程作为资源分配的得,系统在运行时会为每个进程分配不同的内存区域

线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径.

若一个进程统一时间可以并行的执行多个线程,则就是支持多线程的.-
线程作为调度和执行得,每个线程拥有独立的运行栈和程序计数器,线程切换的开销较小
一个进程中的多个线程共享相同的内层单元/内存地址->它们从统一堆中分配对象,可以访问相同的变量和对象.这就使得线程间的通信更简便,高效.但多个线程操作共享的系统资源可能就会带来安全隐患.

单核,多核CPU

早期的CPU多是单核的,也就是一个核心,而如今经常听到16核心32线程的CPU,也就是如今的CPU的核心数增加了,那么单核于多核CPU有什么区别呢?

单核心CPU那么也就只有一个核心(core),那么同一时间,CPU只能执行一个线程.

那么为什么我们能在单核心CPU电脑上运行多个程序并且感觉他们是在同时运作的呢?

是因为CPU在这些线程之间进行高速的切换,CPU将给每个任务分配一个时间片(大概10ms),执行完这一个时间片CPU就会切换到下一个线程执行,只要CPU切换的速度够快就可以近似的认为这些线程在同时执行.因此单核CPU执行多线程其实并不是真正意义上的多线程,而是线程切换.因此单核CPU执行多线程总会有某个线程处于等待(wait)状态,只不过是看起来像同时运行而已.因此单核心CPU执行多线程反而运行速度更慢,但是即使这样,单核心CPU也依旧需要多线程是因为对于一些需要交互的程序,如果只是单线程,那么用户的体验极差(试想一下提前加载好的页面用起来舒服还是你点完必须要等待一下的舒服)

多核心CPU意味着有多个的核心,也就多个线程可以在多个CPU上被执行,只有在多核心CPU上的多线程才能称为真正意义上的多线程,多线程才能真正的发挥出真正的优势.

并行,并发

并行:多个CPU同时执行多个任务
也就是上面多核心CPU中所说的多个CPU同时被分配执行多个线程.
并发:一个CPU(采用时间片)同时执行多个任务
也就是上文说的单核心CPU在多个任务之间按分配时间片的方式在多个任务之间来回切换执行
多核心CPU既可以并发又可以发生并行,单核心CPU只能发生并发

Java中创建多线程的四种方法

概念了解差不多了就开始上代码吧
在jdk1.5之前,创建线程只有两种方法
1: 一个是将一个类声明为Thread的子类。 这个子类应该重写Thread类的run方法 然后可以分配并启动子类的实例。
2:另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。
Thread
以下是官方API,同时也建议简单的看一眼Thread的结构(有惊喜)

class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

Runnable

 class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }
		@Override
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }
 

在一个类中重写好Runnable接口的run方法之后,就可以创建这个类的实例并且调用start方法.使用方法如下

public static void main(String[]args)
{
	PrimeThread pt=new PrimeThread(10L);
	pt.start();//必须调用start方法才能开启一个线程
				//直接调用run方法不会开启新的线程但是会执行run方法体内部的程序
	PrimeRun pr=new PrimeRun(10L);
	pr.start();
}

在jdk1.5之后,增加了两种创建线程的新方法
1:实现Callable接口并且重写其call方法.Callable接口类似于Runnable ,不过call方法不仅有一个泛型的返回值,还可以抛出Exception类型异常.因此如果需要有返回值或需要异常处理,可以考虑使用Callable.
2:线程池方法,目前使用的最多的一种Executors的工厂方法newFixedThreadPool(int nThread)将返回一个ExecutorService对象,这个对象有一个submit()方法可以传递实现Callable接口并重写了call方法的一个实例,并将执行call方法,或传递一个实现了Runnable接口并重写了run方法的一个实例.当方法体执行完毕后,并执行run方法.最后如果当前线程执行完毕任务我们需要回收线程池时,可以使用shutdown()方法关闭线程池.

Future submit(Callable task)
提交值返回任务以执行,并返回代表任务待处理结果的Future。
Future<?> submit(Runnable task)
提交一个可运行的任务执行,并返回一个表示该任务的处理结果的Future。
void shutdown()
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。

Callable

package MultiThread_ch8.MultithreadingDemo.CallableandPool;

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

/**
 * @author: Serendipity
 * Date: 2022/1/11 12:21
 * Description:
 * callable相对于runnable的优势
 * 1:可以抛出异常
 * 2:可以有返回值
 * 3:可以使用泛型
 */
//第一步:实现callable接口 callable接口实现了future和runnable接口
//class  UseCallable implements Callable{ //不使用泛型则默认call方法返回值类型为Object
class UseCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {//可以抛出异常
        return 1;
    }
}
public class CallableImp {
    public static void main(String[] args) {
        //第二部创建一个实现了callable接口的对象
        UseCallable c = new UseCallable();
        //第三步将该对象作为参数传递给FutureTask接口 FutureTask可用于包装Callable或Runnable对象。
        FutureTask<Integer> ft=new FutureTask(c);
        //第四步将FutureTask对象传递给Thread并且调用start方法 
        new Thread(ft).start();
        try {
            //由于Callable接口的call方法有返回值因此返回值可以使用
            Integer obj=ft.get();
            System.out.println(obj);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

ThreadPoolExecutor

package MultiThread_ch8.MultithreadingDemo.CallableandPool;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author: Serendipity
 * Date: 2022/1/11 12:32
 * Description:好处:
 * 1:提高响应速度
 * 2:降低资源消耗
 * 3:便于线程管理
 * corepoolsize:核心池大小
 * maxmumpoolsize:最大线程数
 * keepalivetime:线程没有任务多长时间后终止
 */
class pool1 implements Runnable{
    @Override
    public void run() {}
}
public class ThreadPool {
    public static void main(String[] args) {
        //开辟一个nThreads大小的线程池
        ExecutorService es = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor tpe= (ThreadPoolExecutor) es;//ThreadPoolExecutor是ExecutorService实现类
        System.out.println(es.getClass());//用这个方法可以知道那个类实现了ExecutorServices接口
//      tpe.setCorePoolSize(100);设置线程池大小
        es.submit(new pool1());//submit方法有三种 一种使用Runnable接口 一种使用Callable接口
        es.shutdown();
        es.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                for (int i = 0; i <= 100; i++) {
                    if(i%2!=0)
                        System.out.println(i);
            }return null;
                }
        });
    }
}

以上部分简单罗列了四种创建多线程的方式,多线程虽妙,但是一个也需要考虑到一些多线程程序有的问题.比如死锁,同步问题

同步问题

同步问题概述
同步问题的发生需要满足以下条件:
1:该程序是一个多线程程序
2:该程序中多个线程访问了一个共享数据
现在假设有一个账户,两个人分三次向这个账户转账,每次1000元,那么如果这是一个单线程程序,一定不会发生同步问题,那么如果是一个多线程程序,那就不一定了.

package MultiThread_ch8.MultithreadingDemo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: Serendipity
 * Date: 2022/1/10 21:17
 * Description:
 */
class account implements Runnable{
    private static int money=0;
        ReentrantLock lock=new ReentrantLock();

    @Override
    public void run() {
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(100);//调用延时是为了提高出现同步问题的可能性
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                money+=1000;
                System.out.println(money);
                }
                }
}
        

public class LearnDemo {
    public static void main(String[] args) {
        account a= new account();
        new Thread(a).start();
        new Thread(a).start();
    }
}

在这里插入图片描述
结果如上,很明显,由于多核心CPU同时执行了两个线程,因此汇款的动作同时执行了,那么同一时刻,1000元同时被存入了进去,例如都是在3000元的时候一起存了进去,就导致存了轮次但是只加了1000元,对于这种问题,我们是不允许存在的,因此Java提供了三中解决方法.

解决同步问题的三种方法

同步代码块
将可能发生同步问题的代码用一个{}包住并加上synchronized(同步锁/同步监视器),则这段代码块变为同步代码块,同步代码块同一时间只能允许持有同步锁的线程进入代码块,其他线程必须等待该线程执行完代码块并且释放同步锁之后才有可能进入同步代码块,否则一直处于阻塞状态.
同时需要注意
synchronized()的括号中的锁必须是所有的需要访问共享数据的线程所共享的一个对象,当然,括号内的锁可以是任何一个类对象,因此你直接写一个this,也没有问题,但是你得确保你的this是唯一一份的,
在这里插入图片描述

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

package MultiThread_ch8.MultithreadingDemo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: Serendipity
 * Date: 2022/1/10 21:17
 * Description:
 */
class account implements Runnable{
    private static int money=0;
    ReentrantLock lock=new ReentrantLock();

    @Override
    public void run() {
        synchronized (this) {//注意这把锁必须是需要访问共享数据的线程所共享的
        {
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(100);//调用延时是为了提高出现同步问题的可能性
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                money+=1000;
                System.out.println(money);
            }
            }
    }
}
public class LearnDemo {
    public static void main(String[] args) {
        account a= new account();
        new Thread(a).start();
        new Thread(a).start();
    }
}

同步方法
同步方法即在一个方法前加上一个synchronized修饰,这个方法就变为一个同步方法.同步方法的锁是所在类的Class对象
代码如下

package MultiThread_ch8.MultithreadingDemo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: Serendipity
 * Date: 2022/1/10 21:17
 * Description:
 */
class account implements Runnable{
    private static int money=0;
    private Object obj=new Object();
    ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {//可能有那个地方少加了花括号 自己补一下...
     addmoney();
        }
    }
    public synchronized void addmoney(){
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(100);//调用延时是为了提高出现同步问题的可能性
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money+=1000;
            System.out.println(money);
        }
    }
}
public class LearnDemo {
    public static void main(String[] args) {
        account a= new account();
        new Thread(a).start();
        new Thread(a).start();
    }
}

重进入锁
ReenrantLock类用来设定重进入锁.

一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
void lock()
获得锁。
void unlock()
尝试释放此锁。

与同步代码块一样,这把重进入锁应该也是唯一的,否则会出现和同步代码块可能出现的问题一样的问题
在这里插入图片描述
在这里插入图片描述

package MultiThread_ch8.MultithreadingDemo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: Serendipity
 * Date: 2022/1/10 21:17
 * Description:
 */
class account implements Runnable{
    private static int money=0;
    private Object obj=new Object();
    ReentrantLock lock=new ReentrantLock();//可以考虑加static 

    @Override
    public void run() {
        lockaddmoney();
    }

    public void lockaddmoney(){
//        ReentrantLock lock=new ReentrantLock();
//        将重进入锁放在函数里面没有用 因为每一次调用这个函数都会新new出来一个重进入锁
        try{
            lock.lock();//从这开始的代码将被锁上 每个线程进入时必须要获得锁
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(100);//调用延时是为了提高出现同步问题的可能性
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                money+=1000;
                System.out.println(money);
            }
        }finally {
            lock.unlock();//使用finally来确保每次都能释放锁
            // 而不会由于异常发生导致锁未释放的问题
        }
    }
}
public class LearnDemo {
    public static void main(String[] args) {
        account a= new account();
        new Thread(a).start();
        new Thread(a).start();
    }
}

通过上面三个代码大概我们大概了解了解决同步问题的三种方法,接下来来了解什么是死锁.
什么是锁-知乎

死锁

“死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。”

例如某个线程现在手上有锁,然后它进入了一个if代码块,经判断后发现该锁持有者不符合要求,因此被wait()挂起,之后该对象释放锁,锁由另一个对象使用结果也不符合要求,循环往复,所有的对象都被挂起,没有对象使用notify()/notifyAll()方法,这样所有的线程全进入阻塞状态,程序既不会继续运行,也不会终止.只能认为手动关闭程序.
接下来是一个死锁的程序
两个线程共享两个StringBuilder(线程不安全),由于线程1获取锁s1后,线程2获取了s2,而线程1需要进入下一个代码块需要锁s2,线程2进入下一个代码块需要锁s2,因此互相等待对方释放锁,导致了死锁

public class DeathLock {
    public static void main(String[] args) {
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();
        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append(1);
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        s1.append("b");
                        s2.append(2);
                        System.out.println(s1.toString());
                        System.out.println(s2.toString());
                    }
                }
            }
        }.start();
        new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2) {
                        s1.append("c");
                        s2.append(3);
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (s1) {
                            s1.append("d");
                            s2.append(4);
                            System.out.println(s1.toString());
                            System.out.println(s2.toString());
                        }
                    }
            }
        }).start();
    }}

运行发现什么也不输出,并且程序也不终止,对于这种情况,我们无能为力,只能手动关闭,因此需要尽可能避免写出可能死锁的程序.
在这里插入图片描述

线程通信

线程通信,顾名思义,即线程之间交互,例如某个线程输出玩某条语句之后,通知另一个线程执行某条语句,常用方法如下:

wait():一旦执行当前方法,当前线程就进入阻塞状态,同时释放同步监视器
notify():一旦执行此方法,将随机唤醒一个被wait的线程,唤醒将按照线程的优先级来选择
notifyAll():执行此方法,唤醒所有的wait线程

  • 说明:
  • 上面的这三个方法是线程通信的方法,这些方法必须放在同步方法或同步代码块中才能使用
  • 并且这三个方法的调用者必须是同步代码块的调用者(this)或当前方法(xxx.class)
    否则出现IlleglMonitorStateException异常
  • 这三个方法定义再java.lang.Object方法中,由于想要调用这个方法那么就得有这个方法,因此直接使用Object类对象来调用最为稳妥
  • Reentrant不是用这三种方法来通信.
  • sleep与wait的异同
  • 同:都使当前线程进入阻塞状态
  • 异:wait必须使用再同步代码块或同步方法中
  • 他们都在同步代码中时wait会释放同步监视器(锁),而sleep不会
  • sleep是thread类下的方法 而wait是object类下的方法

接下来是一个简单的程序:
Baozi类

package MultiThread_ch8.MultithreadingDemo.BaoziDemo;

public class Baozi {
    public String pi;
    public String xian;
    public boolean flag;
    public Baozi(){}
    public Baozi(boolean flag) {
        this.flag = flag;
    }

    public Baozi(String pi, String xian) {
        this.pi = pi;
        this.xian = xian;
    }
}

Baozipu类

package MultiThread_ch8.MultithreadingDemo.BaoziDemo;

public class Baozipu extends Thread{
    private Baozi bz;
    public Baozipu(Baozi bz)
    {
        this.bz=bz;
    }
    public void run() {
        int count=0;
        while (true)
        {
            synchronized(bz)
            {   //有包子 此时应该停止 等客人吃完
                if(bz.flag)
                {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没有包子 应该制作包子
                System.out.println("需要制作包子等我一会");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(count%2==0)
                {
                    bz.pi="南瓜";
                    bz.xian="猪肉馅";
                    count++;
                }else if(count%2==1)
                {
                    bz.pi="冬瓜";
                    bz.xian="鸭肉";
                    count++;
                }
                System.out.println("包子做好了"+bz.pi+bz.xian+"的包子");
                bz.flag=true;
                bz.notify();
                System.out.println("提示客人去吃");
            }
        }
    }
}

Chihuo类

package MultiThread_ch8.MultithreadingDemo.BaoziDemo;

public class Chihuo extends Thread{
    private Baozi bz;
    public Chihuo(){}
    public Chihuo(Baozi bz)
    {
        this.bz=bz;
    }
    @Override
    public void run() {
        while (true)
        {
            synchronized (bz)  //synchronized(Chihuo.class)//synchronized(this)都可以不过不是再这里
            {//有包子吃包子
                if(bz.flag)
                {
                    System.out.println("正在吃"+bz.pi+bz.xian+"包子");
                    System.out.println("包子吃完了,在做一个");
                    System.out.println("------------------------");
                    bz.flag=false;
                    bz.notify();//唤醒wait的线程 此时当前线程继续向下执行
                }
                //没有包子就只能等待做包子
                try {
                    bz.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Baozi bz=new Baozi(false);
        //如果要使用同步代码块,那么就要求需要同步的部分使用同一把锁(又叫监视器)
        new Baozipu(bz).start();
        new Chihuo(bz).start();
    }
}

运行可以发现程序不断运行且不终止,可以多实现几个线程来互相交互,方式一样
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-14 01:50:01  更:2022-01-14 01:50:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:00:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码