欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

javaSE基础知识之线程

程序员文章站 2022-03-20 22:53:40
欢迎留言学习交流文章目录 一、基础概念 二、创建线程的三种方式 三、线程的基本方法学习 四、线程模型基础学习 五、创建同步(synchronize) 六、线程死锁示例 七、线程经典范例:两个线程实现交替执行 八、线程同步4个问题的探讨 九、线程生命周期示意图一、基础概念 1.进程:指......

欢迎留言学习交流

文章目录

          一、基础概念

          二、创建线程的三种方式

          三、线程的基本方法学习

          四、线程模型基础学习

          五、创建同步(synchronize)

          六、线程死锁示例

          七、线程经典范例:两个线程实现交替执行

          八、线程同步4个问题的探讨

          九、线程生命周期示意图

 

一、基础概念

       1.进程:指一个程序的运行,它是操作系统进行资源分配的最小单元。

       2.线程:指一个程序中的执行场景或执行单元。

       3.单核CPU:同一个时间点,只能执行一个线程。

       4.多核CPU:同一个时间点,可以并发执行多个线程。

 

二、创建线程的三种方式

       1.继承 extends Thread,需要重写run()方法

package com.study.thread;

/**
 * 实现线程的方式1:继承java.lang.Thread,重写其run()方法
 */
public class SecondThreadDemo {

    public static void main(String[] args) {
        / 1.创建线程对象
        ExtendsThreadDemo extendsThreadDemo=new ExtendsThreadDemo();

        // 2.启动线程
        /*
            start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,代码执行的任务是开辟栈空间即结束
            线程启动成功后会自动调用run()方法,并且run方法在分支栈的栈底部(压栈)
            run方法所在的栈和main方法所在的栈是平级的
            如果使用extendsThreadDemo.run()调用就是普通方法的调用,即是在main线程中
         */
       // 普通调用类的方法
       //extendsThreadDemo.run();
       // 启动线程
       extendsThreadDemo.start();

        for(int i=0;i<20;i++){
            System.out.println("主线程执行序号为:"+i);
        }
    }
}

/**
 * 实现线程的方式1:继承java.lang.Thread,重写其run()方法
 */
class ExtendsThreadDemo extends Thread{

    @Override
    public void run() {
        for(int i=0;i<20;i++){
            System.out.println("继承Thread执行线程序号为:"+i);
        }
    }
}


       2.实现 Runnable接口,需要重写run()方法

package com.study.thread;

/**
 * 实现线程的方式2:实现java.lang.Runnable接口。推荐使用第二种方式
 */
public class SecondThreadDemo {

    public static void main(String[] args) {
         // 1.创建实例线程对象
        ImplementsRunnableDemo implementsRunnableDemo=new ImplementsRunnableDemo();
        // 2.创建线程
        Thread thread=new Thread(implementsRunnableDemo);
        // 3.启动线程
        thread.start();

        for(int i=0;i<20;i++){
            System.out.println("主线程执行序号为:"+i);
        }
    }

/**
 * 实现线程的方式2:实现java.lang.Runnable接口
 */
class ImplementsRunnableDemo implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<20;i++){
            System.out.println("实现接口Runnable执行线程序号为:"+i);
        }
    }
}

       3.实现Callable接口,重写其call()方法

package com.study.thread;

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

/**
 * 第三种方式创建线程,实现Callable接口:
 *      该方式可以获取线程的返回值
 *      缺点是效率低,造成当前线程阻塞
 */
public class ThreeThreadDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1.创建一个Callable实现类对象
        CallableExample callableExample = new CallableExample();
        // 2.创建一个未来任务类
        FutureTask task=new FutureTask(callableExample);
        // 3.创建线程
        Thread t=new Thread(task);
        // 4.启用线程
        t.start();
        // 5.获取返回结果(会导致当前线程阻塞)
        Object obj = task.get();
        // 6.输出获取的结果
        System.out.println("返回的结果:"+obj);

    }


}

/**
*创建实现类
*/
class CallableExample implements Callable{

    @Override
    public Object call() throws Exception {
        System.out.println("返回结果值:");
        return 9;
    }
}
       4.总结:

                     (1)线程的创建也可以用匿名对象,减少代码量。

                     (2)三种创建线程的方式,一般推荐使用第二种,实现接口的方式,因为java不能多继承,但是可以多实现。第三种方式是在对执行结果需要返回值时才使用。

                     (3)main方法执行也是一个线程,默认名是main,进程执行时,垃圾回收线程也会执行

三、线程方法的基本学习

      1.常用线程的相关属性及其方法

package com.study.thread;

/**
 * 线程方法学习
 */
public class ThreadFieldDemo {

    public static void main(String[] args){

        // 创建实现接口的实例对象
        ThreadFieldExample threadFieldExample=new ThreadFieldExample();
        // 创建线程
        Thread thread=new Thread(threadFieldExample);

        // 属性:获取类加载器
        ClassLoader contextClassLoader = thread.getContextClassLoader();
        // 属性:获取线程的标志符
        long id = thread.getId();

        // 静态属性:获取当前线程的名字(这是一个静态方法)
        String name1 = Thread.currentThread().getName();
        System.out.println("输出当前线程(即main线程)的名字为:"+name1);

        // 名字属性:设置线程的名字
        thread.setName("实例线程");
        // 名字属性:获取线程的名字
        String name = thread.getName();

        // 优先级属性:获取优先级
        int priority = thread.getPriority();
        // 优先级属性:优先级设置(默认是5,即Thread.NORM_PRIORITY,最小是1=Thread.MIN_PRIORITY,最大是10=Thread.MAX_PRIORITY)
        thread.setPriority(Thread.MAX_PRIORITY);

        // 状态属性:判断是否还是活动的
        boolean alive = thread.isAlive();

        // 启动属性:启动线程
        thread.start();
        // 中断属性:判断是否中断
        boolean interrupted = thread.isInterrupted();
        // 中断属性:中断线程
        thread.interrupt();

        try {
            // 属性:睡眠,可以间隔特定的时间执行,阻塞的是当前线程,即main线程
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("1秒之后执行这句话");
        // 执行主线程的语句
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"正在执行....,正在打印:"+i);
        }
    }

   
class ThreadFieldExample implements Runnable{

    @Override
    public void run() {
        for(int i=1;i<=10;i++){
            try {
                // 属性:睡眠,可以间隔特定的时间执行(这里是1000毫秒,即1秒),阻塞的是当前线程
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 获取当前执行线程的名字
            String name = Thread.currentThread().getName();
            System.out.println(name+"执行中.......,正在打印数字:"+i);
        }
    }
}

      2.结束线程执行的另一种方式

package com.study.thread;

/**
 * 1.终止线程执行的一种方式:使用标记符
 *
 * 2.线程调度:
 *      抢占式调度模型:线程的优先级越高,抢占到的CPU时间片的概率就高一些/多一些,java采用的就是抢占式调度模型
 *      均分布调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样
 * 3.常用的java线程调度方法:setPriority(int newPriority)设置线程的优先级
 *      最低1,最高10,默认是5
 * 4.static void yield() 让位方法:暂停当前正在执行的线程,回到就绪状态,之后可能又会马上执行
 *
 * 5.合并线程:void join(),当前线程阻塞,加入线程执行,直至执行结束
 *
 */
public class ThreadStopDemo {

    public static void main(String[] args) throws InterruptedException {
        StopExample stopExample = new StopExample();
        Thread thread=new Thread(stopExample);
        thread.start();

        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stopExample.flag=false;

        Thread t2=new Thread(new YieldExample());
        t2.start();

        for(int i=0;i<10;i++){
            if(i==5){
                // 主线程让步,重新抢CPU时间片执行
                Thread.yield();
            }else if(i==3){
                // t2线程先执行完,才继续执行main线程:join(long millis,int nanos),millis是毫秒,nanos是纳秒
                t2.join();
            }
            System.out.println(Thread.currentThread().getName()+"执行中....,,正在打印数字:"+i);
        }

    }
}

class StopExample implements Runnable{
    // 执行标记
    boolean flag=true;
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            if(flag){
                System.out.println("当前线程执行中..."+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                System.out.println("保存最后的数据");
                return;
            }
        }
    }
}

class YieldExample implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"执行中....,,正在打印数字:"+i);
        }
    }
}

            3.守护线程

package com.study.thread;

/**
 * 守护线程:后台线程,类似于垃圾回收线程
 * 用户线程和守护线程
 *
 * 1.守护线程的特点:
 *      1)一般守护线程是一个死循环
 *      2)自动结束
 * 2.使用
 *      1)数据的自动备份(配合定时器)
 *      2)所有线程结束,它也会自动结束
 */
public class ThreadDaemonDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread t=new DaemonExample();
        t.setName("备份数据线程");

        // 属性:设置为守护线程
        t.setDaemon(true);
        t.start();

        // 主线程
        for(int i=0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+"正在执行中....."+i);
            Thread.sleep(1000);
        }
    }
}

class DaemonExample extends Thread{
    @Override
    public void run() {
        int i=0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + "正在执行中....." + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
     4.定时器学习
package com.study.thread;

/**
 * 间隔特定的时间,执行特定的程序
 *
 * 1.使用sleep
 * 2.使用java.util.Timer
 * 3.使用spring的springTask
 */
public class TimerDemo {

    public static void main(String[] args) {

        // 1.创建定时对象
        Timer timer = new Timer();
        // 2.创建守护定时对象
        //Timer timer1 = new Timer(true);
        // 3.指定定时任务(定时任务、执行时间、间隔多久执行一次)
        Date date = new Date();
        // 创建定时任务方式1:
        timer.schedule(new MyTimerTask(),date,1000);
        // 创建定时任务方式2:
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello you");
            }
        },date,1000);
    }

}

class MyTimerTask extends TimerTask{

    @Override
    public void run() {
        System.out.println(LocalDate.now().getDayOfYear());
    }
}

四、线程模型基础学习

 * 1.线程安全问题发生的条件
 *      1)多线程并发
 *      2)有共享数据
 *      3)共享数据有修改行为
 * 2.解决方法(线程同步机制)
 *      1)线程排队执行,即不能并发(专业术语是线程同步)
 *      2)会导致效率有点降低
 * 3.线程模型
 *      1)异步编程模型:线程并发执行
 *      2)同步编程模型:线程排队执行(同步执行)
 *
 * 4.开发中如何解决线程安全问题
 *      1)在必须使用的情况才选择同步机制
 *      2)尽量使用局部变量代替实例变量和静态变量
 *      3)对于实例变量,使用创建多个对象
 

五、线程同步(synchronize)

package com.study.thread;

import com.study.bean.Account;

/**
 * 模拟两个线程对同一个账户执行
 *
 * 1.java中的三大变量
 *      1)实例变量:在堆中(多线程共享时,可能存在线程安全问题)
 *      2)静态变量:在方法区(多线程共享时,可能存在线程安全问题)
 *      3)局部变量:在栈中(不会有线程安全问题)
 * 2.synchronize()可以存放的位置:方法、内部区域块
 *      1)放在方法上,共享对象只能是this,缺点,扩大同步代码块,效率会降低,好处就是减少代码量。使用原则:整个方法体需要同步,且同步对象是this则使用
 *      2)在静态方法上使用:表示类锁永远只有一把
 *      3)代码块上:提高执行效率且灵活
 * 3.局部变量使用:StringBuilder,StringBuffer效率低
 * 4.ArrayList/HashMap/HashSet是非线程安全的
 * 5.Vector/HashTable是线程安全的
 *
 */
public class ThreadAccountDemo {

    public static void main(String[] args) {
        // 1.创建账户对象
        Account account = new Account("preston", 20000);

        // 2.创建两个线程
        Thread t1=new AccountExample(account);
        Thread t2=new AccountExample(account);
        t1.setName("t1");
        t2.setName("t2");

        // 3.启动线程
        t1.start();
        t2.start();
    }

}

class AccountExample extends Thread{
    private Account account;

    public AccountExample(Account account){
        this.account=account;
    }

    @Override
    public void run() {
        double money=2000;
        account.withDraw(money);
        System.out.println(Thread.currentThread()+" 对账户:"+account.getName()+"取款成功,余额为:"+account.getBalance());
    }
}


// Account类
package com.study.bean;

public class Account {

    private String name;

    private double balance;

    public Account(){}

    public Account(String name, double balance) {
        this.name = name;
        this.balance = balance;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /*
     * 当多线程对同一个账户操作时,可能发生线程安全问题
     * 线程同步语法:
     *      synchronized(括号中的数据必须是多线程共享的数据,才能达到多线程排队){线程同步块}
     */
    public void withDraw(double money){
        // 测试发现放在这里并不是完全安全的(需要添加Thread.sleep())解决
        synchronized (this) {
            // 取款之前
            double before = this.getBalance();
            // 取款之后
            double after = before - money;
            try {
                Thread.sleep(20);// 没有该语句还是存在问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 更新账户余额
            this.setBalance(after);
        }

    }
}

六、线程死锁示例

package com.study.thread;

/**
 * 死锁实例:该例子程序中的两个线程已进入死锁状态
 * 尽量不要嵌套使用synchronize
 */
public class ThreadDeadLockDemo {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();

        Thread t1=new DeadLockExample1(object1,object2);
        Thread t2=new DeadLockExample2(object1,object2);

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

    }
}

class DeadLockExample1 extends Thread{
    private Object object1;
    private Object object2;

    public DeadLockExample1(Object object1,Object object2){
        this.object1=object1;
        this.object2=object2;
    }

    @Override
    public void run() {
        synchronized (object1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object2){
                System.out.println("实验1......");
            }
        }
    }
}

class DeadLockExample2 extends Thread{
    private Object object1;
    private Object object2;

    public DeadLockExample2(Object object1,Object object2){
        this.object1=object1;
        this.object2=object2;
    }

    @Override
    public void run() {
        synchronized (object2){
            synchronized (object1){
                System.out.println("实验2......");
            }
        }
    }
}

七、线程经典范例:两个线程实现交替执行

package com.study.thread;

import java.util.List;

/**
 * 1.wait方法调用是对象调用,使当前对象上的线程进入等待状态,直到最终调用notify方法
 * 2.notify方法是唤醒在该对象上等待的线程
 */
public class ThreadProductDemo {
    public static void main(String[] args) {
        Num num=new Num();
        Thread t1=new Thread(new Productor(num));
        Thread t2=new Thread(new Consumer(num));

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

    }
}

class Productor implements Runnable{
    private Num num;

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

    @Override
    public void run() {
        while (true){
            synchronized (num) {
                if(num.i %2==0){// 偶数释放锁
                    try {
                        // 当前线程进入等待状态,并且释放num对象的锁
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"是奇数线程输出:"+num.i);
                num.i++;
                num.notify();
            }
        }
    }
}

class Consumer implements Runnable{

    private Num num;

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

    @Override
    public void run() {
        while (true){
            synchronized (num) {
                if(num.i %2 !=0){// 奇数释放锁
                    try {
                        // 当前线程进入等待状态,并且释放num对象的锁
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"是偶数线程输出:"+num.i);
                num.i++;
                num.notify();
            }
        }
    }
}

class Num{
    int i;
}

八、线程同步4个问题的探讨

1.实例1:doPlay()方法的执行不需要等待doStudy()方法执行完。因为doPlay()方法并没有被锁,不需要获得锁就可以执行

package com.study.thread;


public class ThreadExamDemo {

    public static void main(String[] args) throws InterruptedException {
        ExamExample examExample = new ExamExample();
        Thread t1=new ThreadExample(examExample);
        Thread t2=new ThreadExample(examExample);

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

        t1.start();
       Thread.sleep(1000);
        t2.start();
    }

}
class ThreadExample extends Thread{
    private ExamExample examExample;

    public ThreadExample(ExamExample examExample){
        this.examExample=examExample;
    }

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("t1")){
            examExample.doStudy();
        }

        if(Thread.currentThread().getName().equals("t2")){
            examExample.doPlay();
        }
    }
}
class ExamExample{
    public synchronized void doStudy(){
        System.out.println("开始学习......");
        try {
            Thread.sleep(1000*2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("学习结束.....");
    }

    public void doPlay(){
        System.out.println("开始玩耍......");
        System.out.println("玩耍结束.....");
    }

}

======================================测试结果============================================
开始学习......
开始玩耍......
玩耍结束.....
学习结束.....

Process finished with exit code 0

2.实例2:doPlay()方法的执行需要等待doStudy()方法执行完。因为doPlay()方法也加了锁

package com.study.thread;


public class ThreadExamDemo {

    public static void main(String[] args) throws InterruptedException {
        ExamExample examExample = new ExamExample();
        Thread t1=new ThreadExample(examExample);
        Thread t2=new ThreadExample(examExample);

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

        t1.start();
       Thread.sleep(1000);
        t2.start();
    }

}
class ThreadExample extends Thread{
    private ExamExample examExample;

    public ThreadExample(ExamExample examExample){
        this.examExample=examExample;
    }

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("t1")){
            examExample.doStudy();
        }

        if(Thread.currentThread().getName().equals("t2")){
            examExample.doPlay();
        }
    }
}
class ExamExample{
    public synchronized void doStudy(){
        System.out.println("开始学习......");
        try {
            Thread.sleep(1000*2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("学习结束.....");
    }
    
    // 加锁
    public synchronized void doPlay(){
        System.out.println("开始玩耍......");
        System.out.println("玩耍结束.....");
    }

}
=====================================测试结果=============================================
开始学习......
学习结束.....
开始玩耍......
玩耍结束.....

Process finished with exit code 0

3.实例3:doPlay()方法的执行不需要等待doStudy()方法执行完。因为调用doPlay()方法对象和调用doStudy()方法的对象不一样

package com.study.thread;


public class ThreadExamDemo {

    public static void main(String[] args) throws InterruptedException {
        ExamExample examExample = new ExamExample();
        ExamExample examExample1 = new ExamExample();
        Thread t1=new ThreadExample(examExample);
        Thread t2=new ThreadExample(examExample1);

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

        t1.start();
       Thread.sleep(1000);
        t2.start();
    }

}
class ThreadExample extends Thread{
    private ExamExample examExample;

    public ThreadExample(ExamExample examExample){
        this.examExample=examExample;
    }

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("t1")){
            examExample.doStudy();
        }

        if(Thread.currentThread().getName().equals("t2")){
            examExample.doPlay();
        }
    }
}
class ExamExample{
    public synchronized void doStudy(){
        System.out.println("开始学习......");
        try {
            Thread.sleep(1000*2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("学习结束.....");
    }
    
    // 加锁
    public synchronized void doPlay(){
        System.out.println("开始玩耍......");
        System.out.println("玩耍结束.....");
    }

}
=====================================测试结果=============================================
开始学习......
开始玩耍......
玩耍结束.....
学习结束.....

Process finished with exit code 0

4.实例4:doPlay()方法的执行需要等待doStudy()方法执行完。因为doPlay()方法和doStudy()方法均加上了锁,而且使用了static修饰,这相当于给类加上了锁(类锁只有一把),无论创建多少个对象,结果都一样,一个一个执行

package com.study.thread;


public class ThreadExamDemo {

    public static void main(String[] args) throws InterruptedException {
        ExamExample examExample = new ExamExample();
        ExamExample examExample1 = new ExamExample();
        Thread t1=new ThreadExample(examExample);
        Thread t2=new ThreadExample(examExample1);

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

        t1.start();
       Thread.sleep(1000);
        t2.start();
    }

}
class ThreadExample extends Thread{
    private ExamExample examExample;

    public ThreadExample(ExamExample examExample){
        this.examExample=examExample;
    }

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("t1")){
            examExample.doStudy();
        }

        if(Thread.currentThread().getName().equals("t2")){
            examExample.doPlay();
        }
    }
}
class ExamExample{
    public synchronized static void doStudy(){
        System.out.println("开始学习......");
        try {
            Thread.sleep(1000*2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("学习结束.....");
    }
    
    // 加锁
    public synchronized static void doPlay(){
        System.out.println("开始玩耍......");
        System.out.println("玩耍结束.....");
    }

}
=====================================测试结果=============================================
开始学习......
学习结束.....
开始玩耍......
玩耍结束.....

Process finished with exit code 0

九、线程生命周期示意图

javaSE基础知识之线程

 

 

 

 

本文地址:https://blog.csdn.net/preston555/article/details/112298240