Java多线程学习之二

一、程序、进程、多任务、线程、多线程及其对比

1、程序(Program),是含有指令和数据的文件,被存储在磁盘或其他数据存储设备中,是静态的代码。

2、进程(Process),是程序的一次执行过程,是系统运行程序的基本单位,是操作系统结构的基础。因此,进程是动态的。每个进程之间是独立的,除非利用某些通讯管道来进行通信,或是通过操作系统产生交互作用,否则基本上各进程不知道彼此的存在。

3、多任务(Multi task)是指在一个系统中可以运行多个进程,即有多个独立的运行的任务,每一个任务对应一个进程。

4、线程(Thread),是程序中一个单一的顺序控制流程,也被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。

5、多线程,就是同时执行一个以上的线程,一个线程的执行不必等待另一个线程执行完才执行,所有线程都可能发生在同一时间。

6、进程属于操作系统范畴,主要是在同一时间段内,可以同时执行一个以上的程序。而线程则是在同一程序内同时执行一个以上的程序段。

7、多任务是针对操作系统而言的,表示操作系统可以同时运行多个应用程序,而多线程是指一个进程而言的,表示一个进程内部可以同时执行多个线程

二、线程的相关API

//获取当前线程的名字
Thread.currentThread().getName()

static ThreadcurrentThread() 返回当前正在运行的线程对象。
ClassLoadergetContextClassLoader() 返回该线程的上下文 ClassLoader。
longgetId() 返回该线程的标识符。
StringgetName() 返回该线程的名称。
intgetPriority() 返回线程的优先级。
voidinterrupt() 中断线程。
booleanisInterrupted() 测试线程是否已经中断。
booleanisAlive() 测试线程是否处于活动状态。
booleanisDaemon() 测试该线程是否为守护线程。
voidjoin() 等待该线程终止。
voidjoin(long millis) 等待该线程终止的时间最长为 millis 毫秒。
voidsetDaemon(boolean on) 将该线程标记为守护线程或用户线程。
voidsetName(String name) 改变线程名称,使之与参数 name 相同。
voidsetPriority(int newPriority) 更改线程的优先级。
static voidsleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
voidstart() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
voidstop() 已过时。当执行此方法时,强制结束当前线程。
static voidyield() 暂停当前正在执行的线程对象,并执行其他线程。

三、线程的调度

1、抢占式调度模型
哪个线程的优先级比较高,抢占的CPU时间片的概率就高一些/多一些,java采用的就是抢占式调度模型。

2、均分式调度模型
平均分配CPU时间片,每个线程占用的CPU时间片长度一样,一切平等。

四、线程的优先级

1、等级:

  • MAX_PRIORITY:10
  • MIN_PRIORITY:1
  • NORM_PRIORITY:5

2、方法:

  • getPriority():返回线程优先级
  • setPriority(int newPriority):更改线程的优先级
/*
关于线程的优先级
 */
public class ThreadTest08 {
    public static void main(String[] args) {
        /*System.out.println(Thread.MAX_PRIORITY);
        System.out.println(Thread.MIN_PRIORITY);
        System.out.println(Thread.NORM_PRIORITY);*/

        Thread.currentThread().setPriority(1);
        System.out.println("main线程优先级--->"+Thread.currentThread().getPriority());
        Thread t=new Thread(new MyRunnable4());
        t.setPriority(10);
        t.setName("支线线程t");
        t.start();
        for(int i=0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
class MyRunnable4 implements Runnable{

    @Override
    public void run() {
        System.out.println("支线线程t优先级--->"+Thread.currentThread().getPriority());
        for(int i=0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

五、Java实现多线程方式

1、继承Thread,重写run()方法

/*
    多线程并发
*/
public class ThreadTest01 {
    public static void main(String[] args) {
        //这是main方法,是主线程,在主栈中运行
        MyThread myThread=new MyThread();
        //启动线程
        myThread.start();
        for(int i=0;i<10;i++){
            System.out.println("主线程--->"+i);
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("分支线程--->"+i);
        }
    }
}

输出结果:
主线程—>0
主线程—>1
主线程—>2
主线程—>3
主线程—>4
分支线程—>0
分支线程—>1
分支线程—>2
分支线程—>3
分支线程—>4
分支线程—>5
分支线程—>6
主线程—>5
主线程—>6
主线程—>7
主线程—>8
主线程—>9
分支线程—>7
分支线程—>8
分支线程—>9

Process finished with exit code 0

另外,要明白启动线程的是start()方法而不是run()方法,如果用run()方法,那么他就是一个普通的方法执行了。

2、实现Runable接口

public class ThreadTest02 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        //MyRunnable r=new MyRunnable();
        //将可运行的对象封装成一个线程对象
        //Thread thread=new Thread(r);
        Thread thread=new Thread(new MyRunnable());//合并代码
        thread.start();
        for(int i=0;i<5;i++){
            System.out.println("主线程--->"+i);
        }
    }
}
//这并不是一个线程类,是一个可运行的类,还不是一个线程
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println("分支线程--->"+i);
        }
    }
}

3、通过匿名内部类的方式实现多线程

/*
    通过匿名内部类的方式实现线程
 */
public class  ThreadTest03 {
    public static void main(String[] args) {
        //采用匿名内部类的方式创建线程对象
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("t线程--->"+i);
                }
            }
        });
        //启动线程
        t.start();
        for(int i=0;i<10;i++){
            System.out.println("main线程--->"+i);
        }
    }
}

4、实现Callable接口方式

与使用runnable方式相比,callable功能更强大些:
runnable重写的run方法不如callable的call方法强大,call方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

package com.guteng.thread;

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

/**
 * @author 古藤老人
 */
public class ThreadTest02 {
    /**
     *创建线程的方式三:实现callable接口。---JDK 5.0新增
     *是否多线程?否,就一个线程
     *
     * 比runable多一个FutureTask类,用来接收call方法的返回值。
     * 适用于需要从线程中接收返回值的形式
     *
     * //callable实现新建线程的步骤:
     * 1.创建一个实现callable的实现类
     * 2.实现call方法,将此线程需要执行的操作声明在call()中
     * 3.创建callable实现类的对象
     * 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
     * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)
     *
     * */
    public static void main(String[] args) {
        //new一个实现callable接口的对象
        MyCallable myCallable = new MyCallable();

        //通过futureTask对象的get方法来接收futureTask的值
        FutureTask futureTask = new FutureTask(myCallable);

        Thread t = new Thread(futureTask);
        t.setName("t线程");
        t.start();

        try {
            //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
            Object sum = futureTask.get();
            System.out.println(Thread.currentThread().getName() + ":" + sum);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

//实现callable接口的call方法
class MyCallable implements Callable {
    
    private int sum=0;
    //可以抛出异常

    @Override
    public Object call() throws Exception {
        for(int i = 0;i<50;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
                sum += i;
            }
        }
        return sum;
    }
}

5、使用线程池的方式

(1)背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

(2)思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)

(3)好处:提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理

(4)解释

  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后会终止

(5)JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
  • void execute(Runnable coommand):在将来某个时间执行给定任务,没有返回值,一般用来执行Runnable
  • Future submit(Callable task):提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
  • void shutdown():关闭连接池。
Executors工具类,线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool()创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor()创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(int corePoolSize)创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

(6)线程池构造批量线程代码如下:

package com.guteng.thread;

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

/**
 * @author 古藤老人
 */
public class ThreadPool {

    /**
     * 创建线程的方式四:使用线程池(批量使用线程)
     * 1.需要创建实现runnable或者callable接口方式的对象
     * 2.创建Executorservice线程池
     * 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
     * 4.关闭线程池
     */

    public static void main(String[] args) {

        //创建固定线程个数为十个的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //new一个Runnable接口的对象
        MyRunnable1 runnable1 = new MyRunnable1();
        MyRunnable2 runnable2 = new MyRunnable2();

        //执行线程,最多十个
        executorService.execute(runnable1);
        executorService.execute(runnable2);
        //适合适用于Runnable

        //executorService.submit();//适合使用于Callable
        //关闭线程池
        executorService.shutdown();
    }
}

class MyRunnable1 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 1) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

六、线程生命周期

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

Java线程具有五中基本状态

(1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

(2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start(),此线程立即就会执行;

(3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

  1. 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
  2. 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
  3. 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意:sleep是不会释放持有的锁)

(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

七、线程相关方法

1、线程休眠
sleep()方法定义在Thread类中,sleep()的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待CPU的调度执行。

public static void sleep(long millis)
静态方法:Thread.sleep(1000);
参数是毫秒
可以实现间隔一段时间,去执行一段特定的代码

/**
 * 合理地终止一个进程
 * @author 古藤老人
 */
public class ThreadTest07 {
    public static void main(String[] args) {
        MyRunnable3 r=new MyRunnable3();
        Thread t=new Thread(r);
        t.setName("t线程");
        t.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        r.run=false;
    }
}
class MyRunnable3 implements Runnable{
    //打一个布尔标记
    boolean run=true;
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            if(run){
                System.out.println(Thread.currentThread().getName()+"--->"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                //终止当前线程
                return;
            }
        }

    }
}


2、线程让步
在Java线程中,yield()方法的作用是让步,它能让当前线程由“运行状态”进入到“就绪状态”,从而让其他具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其他具有相同优先级的线程就一定能获得执行权;也有可能是当前线程有进入到“运行状态”继续运行。

/**
 * 让位,当前线程暂停,回到就绪状态,让给其他线程
 * @author 古藤老人
 */
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t=new Thread(new MyRunnable5());
        t.setName("t线程");
        t.start();
        for(int i=1;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
class MyRunnable5 implements Runnable{

    @Override
    public void run() {
        for(int i=1;i<10;i++){
            //每10个让位一次
            if(i%2==0){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

3、合并线程
join方法调用后当前线程会等待目标线程死亡后才会继续运行

/**
 * @author 古藤老人
 */
public class ThreadTest11 {
    public static void main(String[] args) {
        System.out.println("main begin");
        Thread t=new Thread(new MyRunnable6());
        t.setName("支线线程");
        t.start();
        //合并线程
        try {
            t.join();//当前线程(main线程)进入堵塞,t线程执行,直到t线程结束,当前线程才可继续执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main over");
    }
}
class MyRunnable6 implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

4、对线程等待和唤醒的方法

(1)在Object.java中,定义了wait(),notify()和notifyAll()等方法。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

(2)Object中的wait()、notify()等函数和synchronized一样,会对“对象的同步锁”进行操作。wait()会使“当前线程”等待,因为线程应该释放它所持有的“同步锁”,否则其他线程获取不到该“同步锁”而无法运行。线程调用wait()之后,会释放它所持有的“同步锁”;而且,等待线程可以被notify()或notifyAll()唤醒。那么notify()是依据什么唤醒等待线程的?或者wait()和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

(3)负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒,但是,它不能立即执行。因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放“该对象的同步锁”之后,等待线程才能获取到“对象的同步锁”,然后继续运行。
  
(4)调用wait()、notify()和notifyAll()的任务在调用这些方法前必须获取对象的锁。

public class ThreadHomework {
    public static void main(String[] args) {
        Num num=new Num(0);
        Thread t1=new Thread(new Producer1(num));
        Thread t2=new Thread(new Producer2(num));

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        t2.start();
    }
}

class Num{
    int i;
    public Num(int i) {
        this.i = i;
    }
}

class Producer1 implements Runnable{
    Num num;

    public Producer1(Num num) {
        this.num = num;
    }

    @Override
    public void run() {
        while(true){
            synchronized(num){
                //num.i是奇数
                if(num.i%2!=0){
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                num.i++;
                System.out.println(Thread.currentThread().getName()+"--->"+num.i);
                num.notifyAll();
            }
        }
    }
}
class Producer2 implements Runnable{
    private Num num;

    public Producer2(Num num) {
        this.num = num;
    }
    @Override
    public void run() {
        while(true){
            synchronized(num){
                //num.i是偶数
                if(num.i%2==0){
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                num.i++;
                System.out.println(Thread.currentThread().getName()+"--->"+num.i);
                num.notifyAll();
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

古藤老人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值