线程基础的一些理解

Posted francisliu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程基础的一些理解相关的知识,希望对你有一定的参考价值。

一、线程的调度与控制

  通常计算机只有一个CPU,CPU在某一个时刻只能执行一条命令,线程只有得到CPU时间片,也就是使用权,才可以执行命令。在单核CPU的机器上,线程并不是并行运行的。java虚拟机主要负责线程调度,取得CPU的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java使用抢占式调度模型。

  分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

  抢占式调度模型:优先级高的线程获取CPU时间片相对会多一些,如果线程的优先级相同,那么会随机选用一个 。

  下面我们将通过代码来了解如何在java代码中使用线程,一般我们在工作中是不会去写多线程的代码,但如果你是写服务器端代码,如在IBM公司或者写机器之间的通信,自己写一个服务器端的代码,那我们就必须考虑多线程了,正常我们用的服务器像tomcat等已经底层实现多线程了。

  1、关于线程的一些基本方法

    (1)获取当前线程对象  Thread.currentThread();

    (2)给线程起名  t.setName("t1");

    (3)获取线程的名字  t.getName();

public class _04线程调度 {
    public static void main(String[] args){
        //如何获取当前线程对象?
        //t保存的内存地址指向的线程是“主线程对象”
        Thread t=Thread.currentThread();
        //获取线程的名字
        System.out.println(t.getName());
        Thread t1=new Thread(new Processor03());
        //给线程起名
        t1.setName("t1");
        t1.start();
        
        Thread t2=new Thread(new Processor03());
        //给线程起名
        t2.setName("t2");
        t2.start();
    }
}
class Processor03 implements Runnable{
    public void run(){
        //t保存的内存地址指向的线程是“t1线程对象”
        Thread t=Thread.currentThread();
        System.out.println(t.getName());
    }
}

  2、线程的优先级

  线程优先级高的线程获取的cpu时间片相对多一些,也就是获得执行的机会会大一些,优先级 1-10

  线程的优先级主要分三种:MAX_PRIORITY(最高级)(10);MIN_PRIORITY(最低级)(1);NOM_PRIORITY(标准)默认(5)

  获取以及设置线程的优先级方法为Thread.getPriority()和Thread.setPriority(),

  从运行结果中可以看到t1线程和t2线程抢夺cpu时间片,由于设置了t1的优先级高一些,所以t1抢夺的时间片会相对多一些,所以执行的会多一些

  技术分享图片

  3、Thrad.sleep(ms),线程休眠的方法,关于线程休眠有如下几点注意:

  1.Thread.sleep;

  2.sleep方法是一个静态方法

  3.该方法的作用,阻塞当前线程(回顾一中线程的执行流),腾出CPU让给其他线程

    技术分享图片

  4.此方法是静态方法,所以"引用."和"类名."是一样的

  下面的代码中,t.sleep等同于Thread.sleep,故阻塞的并不是t线程,而是阻塞的主线程。

public class _07面试题 {
    public static void main(String[] args) throws Exception{
        Thread t=new Processor07();
        t.setName("t");
        t.start();
        //等同于Threadsleep
        //Thread.sleep()是静态方法,所以t.sleep()等同于Thread.sleep()
        //所以t线程是不会阻塞的,阻塞的是主线程
        t.sleep(5000);
    }
}

class Processor07 extends Thread{
    public void run(){
        for(int i=0;i<3000;i++){
            System.out.println(Thread.currentThread().getName()+"---->"+i);
        }
    }
}

  5.中断休眠的方法

  (1)使用异常机制 t.interrupt(),此interrupt会触发Interrupted异常可以从休眠中退出。

    技术分享图片

   (2)正常终止,正确的停止一个线程,在外面增加一个表示位

    技术分享图片

二、线程的同步(加锁)   

  1、异步编程模型与同步编程模型。
  异步编程模型:t1线程执行t1的,t2线程执行t2的。两个线程谁也不等谁
  同步编程模型:t1线程和t2线程执行,当t1线程必须等t2线程执行结束之后,t1线程才能执行,这是同步编程模型
  2、什么时候同步呢?为什么要引入线程同步呢?
  1.为了数据安全。暂时就不考虑效率了,尽管应用程序的使用率降低,但是为了保证数据是安全的必须加入同步机制,线程是同步的,所以程序就变成了单线程,就像两条直线

原来是平行,现在要接在一起变成一根线。
  比如现实生活中,我和你共享一个银行账户,此时这个账户里有5万块钱,今天我从这个账户里取了两万块钱,刚取出来,在账户还没有减掉2万块钱的时候(此时账户里还是5万,但我已经取了2万),
你也来访问这个账户取了3万块,这时你返回给账户是5万减3万,账户还有2万,这是取钱的反馈信息也返回,账户还剩3万。最后结果是,账户原本有5万,我取了2万,你取了3万,账户里还有3万,这

样就出现的账户安全问题,下面我们会用这个例子来做说明。

  2.什么条件下要使用线程同步?
  (1):必须是多线程环境
  (2):多线程环境共享同一个数据
  (3):共享的数据涉及到修改操作

    以下程序演示取款例子,首先我们看一下不使用线程同步机制,多线程同时对同一个账户进行取款操作,会出现什么问题?

    首先我们创建一个Account类模拟账户,里面有账户名称以及账户金额,里面提供一个withdraw()的提款方法,之后用创建Processer()类实现Runnable接口实现多线程

在构造器中加入账户类,run()方法中使用提款方法,在主方法中创建一个新账户,创建多线程,我们为了能够实现会出现安全问题,在withdraw()方法中加了一个1s的sleep延时,

这样当t1线程,或者t2线程执行时,一个线程执行后,延时1s内,另一个线程会运行这个方法,另一个线程取到的账户余额就不是第一个线程剩下的余额。而是最开始的余额,我

们看到最后的结果是t1去了1000 t2取了1000 但是还剩4000元

public class _00线程的同步加锁 {
    public static void main(String[] args) throws Exception{
        //创建一个公共账户
        Account act=new Account("actno--01",5000.0);
        //创建线程对同一个账户取款
        Porcessor p=new Porcessor(act);
        Thread t1=new Thread(p);
        Thread t2=new Thread(p);
        
        t1.start();
        //t1.sleep(2000);
        t2.start();
    }
}
//取款线程
class Porcessor implements Runnable{
    //账户
    Account act;
    //Constructor
    Porcessor(Account act){
        this.act=act;
    }
    public void run(){
        act.withdraw(1000.0);
        System.out.println("取款1000成功,余额:"+act.getBalance());
    }
}
//账户
class Account{
    private String actno;
    private double balance;
    public Account(){};
    public Account(String actno,double balance){
        this.actno=actno;
        this.balance=balance;
    }
    //setter and getter方法
    public String getActno(){return this.actno;}
    public void setActno(String actno){this.actno=actno;}
    public double getBalance(){return this.balance;}
    public void setBalance(double balance){this.balance=balance;}
    //对外提供一个取款的方法
    public void withdraw(double money){//对当前账户进行取款
        double before=this.balance;
        double after=before-money;
        //延迟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //更新
        this.setBalance(after);
    }
}

运行结果:

取款1000成功,余额:4000.0
取款1000成功,余额:4000.0

  3.使用线程同步 synchronized关键字

  同步原理:

  原理:t1线程和t2线程,t1线程执行到withdraw()方法里,遇到了synchronized关键字,就会去找this对象锁(本质是每个对象上都有一个0和1的标志,我们形象的称它为锁),如果找到this对象锁则进入同步语句块中执行程序,当同步语句块中的代码结束之后,t1线程归还this的对象锁。
  在本程序中,t1线程执行同步语句块的过程中,如果t2也过来执行以下代码,也遇到了synchronized关键字,也会去找this的对象锁,但是该对象锁被t1持有,只能在这等待this对象锁归还才能执行t2线程,

也就是说t1线程在sleep()1s的时候,t2线程也执行到这里,发现t1还持有锁,只能等t1线程把账户中的钱都减掉才可以执行t2线程的代码。我们发现运行的结果是正确的。

  但我们在运行的过程中会发现,运行的时间变长了。因为t2线程会等待。

class Account01{
    private String actno;
    private double balance;
    
    public Account01(){};
    public Account01(String actno,double balance){
        this.actno=actno;
        this.balance=balance;
    }
    //setter and getter方法
    public String getActno(){
        return this.actno;
    }
    public void setActno(String actno){
        this.actno=actno;
    }
    public double getBalance(){
        return this.balance;
    }
    public void setBalance(double balance){
        this.balance=balance;
    }
    //对外提供一个取款的方法
    //public void synchronized withdraw(double money){
    //} synchronized添加到成员方法上,线程拿走的也是this对象锁
    public void withdraw(double money){//对当前账户进行取款
        //把需要同步的代码,放到同步语句块中 
        synchronized(this){
            double before=this.balance;
            double after=before-money;
            //延迟
            try{Thread.sleep(1000);}catch(Exception e){}
            //更新
            this.setBalance(after);
        }
    }
}

运行结果:

取款1000成功,余额:4000.0
取款1000成功,余额:3000.0

  4.使用setDaemon()方法,添加守护线程。

  从线程类型上分可以分为,用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期
  只要有一个用户线程存在,那么守护线程就不会结束,例如java中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束,

守护线程一般都是无限执行的。我们可以使用setDaemon()方法将一个线程变为守护线程

  下面的程序如果没有setDaemon()方法,那么t1线程会一下运行下去,但是加了setDaemon()方法,t1变成守护线程,当主线程运行完后,t1线程就会结束。

public class _00守护线程 {
    public static void main(String[] args) throws Exception{
        Thread t1=new Processor();
        t1.setName("守护线程");
        //将t1这个用户线程修改成守护线程
        t1.setDaemon(true);
        t1.start();
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-->"+i);
            Thread.sleep(1000);
        }
    }
}
class Processor extends Thread{
    
    public void run(){
        int i=0;
        while(true){
            i++;
            System.out.println(Thread.currentThread().getName()+"-->"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 












以上是关于线程基础的一些理解的主要内容,如果未能解决你的问题,请参考以下文章

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

C# 线程线程池Task概念+代码实践

Java多线程一些基础知识

多个用户访问同一段代码

多线程 Thread 线程同步 synchronized

JAVA 线程基础(上)