JavaSE中的线程

一、线程的概念

(1)程序,进程和线程

程序pragam: 为解决某种问题,使用计算机语言编写的一系列指令(代码)的集合。本章中的程序,特指的是静态的,安装在硬盘上代码集合。
进程:  运行中的程序(被加载到内存中), 是操作系统进行资源分配的最小单位
线程:  进程可以进一步细化为线程,是进程内一个最小执行单元(具体要做的事情),是cpu进行任务调度的最小单位.
    运行中的QQ 就是一个进程, 操作系统会为这个进程分配内存资源,一个聊天窗口就认为是一个线程, 这多个聊天窗口可以同时被cpu执行,但是这些聊天窗口属于进程, 线程是属于进程的.
    早期没有线程,早期cpu执行的时候,是以进程为单位执行,进程单位还是比较大的,当一个进程运行时,其他的进行就不能执行,所以后来,将进程中的多个任务,细化为线程,cpu执行单位,也从进程转为更小的线程.

(2)进程和线程的关系

一个进程中可以包含多个线程
一个线程只能隶属于一个进程,线程不能脱离进程存在
一个进程中至少有一个线程(即主线程) java中的main方法,就是用来启动主线程
在主线程中可以创建并启动其他线程
所有线程都共享进程内存资源.

二、线程的创建与常用方法

(1)创建线程

创建线程共有三种方式

1、继承Thread类

写一个线程类,通过继承Thread类并重写run方法,并在程序中创建类并调用的方法进行使用

例:

myThread类


public class MyThread extends Thread{
/*
java中创建线程的方法
    写一个类继承java.lang.Thread
    重写run()
 */
    @Override
    public void run() {
        /*
        线程中要执行的任务都要写到run方法中,或者在run方法中进行调用
         */
        for(int i=0;i<1000;i++){
            System.out.println("MyThread"+i);
        }
    }
}

测试类:

import com.wbc.Thread.多线程.方式1继承Thread.MyThread;

public class threadTest {
    public static void main(String[] args) {
        //创建并启动线程
        MyThread myThread =new MyThread();
            //myThread.run();这不是启动线程,只是一个方法调用,本质仍是单线程
        myThread.start();

        for(int i=0;i<1000;i++){
            System.out.println("main:"+i);
        }
        /*
        MyThread941
        MyThread942
        MyThread943
        main:957
        main:958
        main:959
        main:960
        MyThread944
        MyThread945
        MyThread946
        MyThread947
         */

    }
}

在需要开启线程的地方new继承了 Thread类的线程类

需要注意的是,使用 线程需要调用的是.start()方法而不是run方法,单纯调用run方法本质还是单线程调用

2、实现Runnable接口

创建一个任务类以实现Runnable接口,通过new Thread(任务类).start启动线程

例:

任务类:

public class Task implements Runnable{
    /*
    java中创建线程方式2
    创建一个类实现Runnable接口
    重写run方法
     */
    @Override
    public void run() {
        for (int i=0;i<10000;i++){
            System.out.println("task:"+i);
        }
    }
}

测试类:

package com.wbc.Thread.多线程.方式2实现接口;

public class TaskTest {
    public static void main(String[] args) {
        //创建任务
        Task task =new Task();
        //创建线程
        Thread thread =new Thread(task);
        thread.start();

        for(int i=0;i<10000;i++){
            System.out.println("main:"+i);
        }
        /*
        main:9448
        main:9449
        main:9450
        task:8954
        task:8955
        task:8956
        task:8957
        task:8958
        task:8959
        task:8960
        main:9451
        main:9452
         */
    }
}

测试发现主线程(main)中for 循环打印的i和线程打印交替进行,证明是双线程

3、实现Callable接口

创建一个任务类以实现Callable接口并重写call方法

FutureTask futureTask =new FutureTask(任务类),创建将来执行任务的对象

new Thread(futureTask).start();

例:

任务类



import java.util.concurrent.Callable;

public class SumTask<T> implements Callable<T> {

    @Override
    public T call() throws Exception {
        Integer i =0;

        for(int j=0;j<100;j++){
                i+=j;
        }

        return (T)i;
    }
}

测试类:

package com.wbc.Thread.多线程.方式3实现Callable接口;

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

public class Test {
    public static void main(String[] args) {
        SumTask<Integer> sumTask =new SumTask<>();
        FutureTask futureTask =new FutureTask(sumTask);
        Thread thread =new Thread(futureTask);
            thread.start();

        try {
            Integer i =(Integer) futureTask.get();//获取返回值
            System.out.println(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
推荐使用实现Runnable接口进行创建线程

        由于java是单继承的,继承了Thread类,就不能继承其他类了。实现Runnable接口避免了单继承的局限性 并且实现Runnable接口进行创建线程更适合多线程共享同一份资源的场景

        当需要使用泛型时可以使用Callable接口

(2)线程的相关方法

Thread表示线程,提供了很多的方法,来对线程程进行控制.
   run(); 线程要执行的任务
   start(); 启动java线程的

   构造方法
   new Thread(Runable runnablee); 接收一个任务对象..
   new Thread(Runable runnablee,String name); 接收一个任务对象,并为线程设置名字

   setName("我的线程1"); 为线程设置名字
   String getName(); 获得线程的名字

   Thread..currentThread();在任务中获得当前正在执行的线程

   setPriority(int  p11)设置优先级  1-10之间  默认是5
   getPriority(); 获得优先级

   sleep(long mm); 让线程休眠指定的时间  毫秒单位
   join(); 让其他线程等待当前线程结束

myThread类

package com.wbc.Thread.多线程.ThreadMethods;

public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
         /*
        Thread中的方法:
            1:run();用来定义线程要执行的任务代码
            2:start();用来启动线程
            3:Thread.currentThread();获取当前线程
            4:getId();获取ID
            5:getName();setName() 获取/设置名字
            6:getState;获取线程状态
            7:getPriority();setPriority();获取/设置线程优先级
              优先级范围1——10;默认为5
            8:Thread.sleep();休眠指定时间
            9:join();等待当前线程执行完毕,其他线程再执行
            10:Thread.yield();//礼让
         */
        MyThread myThread =new MyThread();
            myThread.setName("线程1");
            myThread.setPriority(4);
            myThread.start();
            //myThread.join();//当代线程终止
        MyThread myThread1 =new MyThread();
            myThread1.setName("线程2");
            myThread1.setPriority(3);
            myThread1.start();

    }
}

测试类:

package com.wbc.Thread.多线程.ThreadMethods;

public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);//休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0;i<1000;i++){
            Thread thread =Thread.currentThread();//获取当前执行的线程
            if(i%10==0){
                Thread.yield();//礼让
            }
            System.out.println(thread.getName()+" "+i);
        }
    }



}

三、单线程与多线程

    当在java程序中有几件不相关的事情同时有机会执行.可以在java中创建多个线程,把一些要执行的任务放在线程中执行,这样的话,都拥有让cpu执行的权利。而如果只需要顺序执行的话则不需要创建多个线程

在正式了解多线程之前先了解一下线程的生命周期

线程的生命周期

线程的生命周期是线程从创建到销毁的过程,期间共经历五个状态

新建状态:新建 new Thread(); 处于新建状态,此状态还不能被执行.
         调用start()启动线程 让线程进入到就绪状态,
就绪状态:获得到cpu执行权后, 线程进入到cpu执行
运行状态:运行中的线程可以被切换,回到就绪状态, 也可能因为休眠等原因进入阻塞状态
阻塞状态:线程休眠时间到了  回到就绪状态
死亡状态:当线程中所有的任务执行完了, 线程也就自动销毁

线程生命周期示意图

 

多线程的概念及其优缺点

在一个程序可以创建多个线程,以执行不同的任务

多线程的优点:提高程序的响应 提高cpu的利用率 改善程序结构,将复杂任务分为多个线程,独立运行

多线程的缺点:线程越多占用内存越多 跟踪管理线程cpu开销变大

多线程的使用场景:

多线程访问操作同一个共享数据(例如 买票 、抢购、秒杀)
需要解决操作共享的问题。解决方法:排队+锁 在关键步骤处,多个线程只能一个一个执行

 添加同步锁

 加锁方式1:

使用synchronized关键字修饰代码块和方法

修饰代码块    

同步对象要求: 多个线程用到的必须是同一个对象,可以是java中任何类的对象.

作用: 用来记录有没有线程进入到同步代码块中

synchronized(同步对象/锁){

        //同步代码块, 一次只允许一个线程进入

}
以实现Runnable接口为例

任务类:

package com.wbc.Thread.多线程.模拟卖票2Runnable;

public class TicketTask implements Runnable{
    int num =10;
    @Override
    public void run() {

        while(true){

            synchronized (this){//由于两个线程对应的是同一个任务对象,所以可以使用this,以及num可以不限制为静态的
                if(num>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(
                            Thread.currentThread().getName()
                                    +"买到了第"+num+"张票");
                    num--;
                }
                else{
                    break;
                }
            }
        }


    }
    
}
测试类:
package com.wbc.Thread.多线程.模拟卖票2Runnable;

public class TicketTest {
    public static void main(String[] args) {
        TicketTask task =new TicketTask();
        Thread t1 =new Thread(task);
                t1.setName("窗口1");
                t1.start();
        Thread t2 =new Thread(task);
                t2.setName("窗口2");
                t2.start();

    }
}
 修饰方法:
   1.锁不需要我们提供了,会默认提供锁对象
   2.synchronized如果修饰的是非静态的方法,锁对象是this
     synchronized如果修饰的是静态方法,锁对象是类的Class对象
    一个类只有一个Class对象.
以继承Thread类为例:

线程类:

package com.wbc.Thread.多线程.模拟卖票1Thread;

public class TicketThread extends Thread{
    static int num =10;//模拟票数有10张
    static TicketThread tickerThread =new TicketThread();



    
    /*
    synchronized 修饰方法时,同步锁对象不需要指定
    同步锁对象会默认提供:
    1、非静态方法锁对象默认是this,会造成有多个this,两个对象
    2、静态方法锁对象是当前类的class对象(类的对象,一个类的对象只有一个)


    */
    @Override
    public void run(){
        while(true){
            if(num<=0){
                break;
            }
            print();
        }
    }

    public static synchronized void print(){
        if(num>0){
            System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
            num--;
        }
    }

}

测试类:

package com.wbc.Thread.多线程.模拟卖票1Thread;

public class TicketTest {
    public static void main(String[] args) {
        TicketThread t1 =new TicketThread();
            t1.setName("张三");
            t1.start();
        TicketThread t2 =new TicketThread();
            t2.setName("李四");
            t2.start();
    }
}
枷锁方式2:

使用jdk中提供的ReentrantLock类实现加锁
ReentrantLock只能对某一段代码块加锁,不能对整个方法加锁

线程类:

import java.util.concurrent.locks.ReentrantLock;

public class TicketThread extends Thread{
    static int num =10;

    //使用Thread需要静态保证只有一个锁对象,实现Runnable接口则不需要,因为只有一个任务对象
    static ReentrantLock reentrantLock =new ReentrantLock();//加锁释放锁需要手动,如果出现异常不会自动释放,所以需要try{}finally{},确保锁会释放
    @Override
    public void run() {
        while(true){
            try{
                reentrantLock.lock();//加锁,其底层状态由0转1;
                if(num>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(
                            Thread.currentThread().getName()
                                    +"买到了第"+num+"张票");
                    num--;
                }
                else{
                    break;
                }
            }finally {
                reentrantLock.unlock();//释放锁,底层状态从1转0
            }

        }
    }

测试类:

package com.wbc.Thread.多线程.模拟卖票3ReentrantLock;

public class TicketTest {
    public static void main(String[] args) {
        TicketThread t1 =new TicketThread();
        t1.setName("张三");
        t1.start();
        TicketThread t2 =new TicketThread();
        t2.setName("李四");
        t2.start();
    }
}
 synchronized 和 ReentrantLock区别
相同点:都实现了加锁的功能

不同点:
1.synchronized 是一个关键字,ReentrantLock是一个类
2.synchronized修饰代码块和方法,ReentrantLock只能修饰代码块
3.synchronized可以隐式的加锁和释放锁,运行过程中如出现了异常可以自动释放
  ReentrantLock需要手动的添加锁和释放锁,建议在finally代码块中释放锁.

 

四、线程通信问题 

经典例题:生产者/消费者问题

    生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待

此类情况,需要线程与线程之间进行通信,而这种通讯在现阶段一般通过在同步代码块的基础上,使用wait,notify对线程进行控制

生产线程

package com.wbc.Thread.多线程.线程通信.生产者消费者问题;

public class ProductorThread extends Thread{
    /*
    生产者线程
     */
    Counter counter;

    public ProductorThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while(true){
            counter.add();
        }
    }
}

消费线程

package com.wbc.Thread.多线程.线程通信.生产者消费者问题;

public class CustomerThread extends Thread{
    /*
    消费者线程
     */
    Counter counter;

    public CustomerThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while(true){
            counter.sub();
        }
    }
}

柜台

package com.wbc.Thread.多线程.线程通信.生产者消费者问题;

public class Counter {
    /*
    柜台:共享数据
     */
    int num = 0;//商品的数量

    /*
    只有一个Counter对象,两个方法都非静态,但用的都是同一把锁
     */

    //负责生产商品的方法
    public synchronized void add(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(num==0){
            num=1;
            this.notify();//唤醒消费者线程
            System.out.println(Thread.currentThread().getName()+":已生产");
        }
        else{

            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    //负责销售的方法
    public synchronized void sub(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(num==1){
            num=0;
            this.notify();
            System.out.println(Thread.currentThread().getName()+":已购买");
        }
        else{
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
/*
Thread.sleep(long time);
    属于Thread类中的方法
    sleep();让线程休眠之后会自动唤醒,并继续排队准备执行
    sleep()不会释放锁

obj.wait();
    属于Object类的中方法,需要锁对象调用
    wait后的线程需要其他线程唤醒(notify();notifyAll();唤醒)
    wait()会自动释放锁
 */

运行类

package com.wbc.Thread.多线程.线程通信.生产者消费者问题;

public class Test {

    public static void main(String[] args) {
        Counter counter =new Counter();//唯一的Counter对象
        CustomerThread customerThread =new CustomerThread(counter);
                customerThread.setName("消费者");
        ProductorThread productorThread =new ProductorThread(counter);
                productorThread.setName("生产者");
        customerThread.start();
        productorThread.start();
    }
}

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北京最后的深情

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

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

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

打赏作者

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

抵扣说明:

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

余额充值