Java多线程基础

1.什么是线程,和进程有哪些区别

进程:一个程序的一次运行,在执行过程中拥有独立的内存单元,而多个线程共享一块内存
线程:线程是指进程内的一个执行单元。
联系:线程是进程的基本组成单位
区别:(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
          (2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
          (3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
          (4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

2.如何创建线程

1.继承Thread类,重写run方法

public class MyThread_01  extends  Thread {

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        MyThread_01  thread_01 = new MyThread_01();
        MyThread_01  thread_02 = new MyThread_01();
        thread_01.start();
        thread_02.start();
    }
    }
}

2.实现Runnable接口,重写run方法

public class MyThread_02 implements   Runnable {

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        MyThread_02  myThread_02= new  MyThread_02();
        Thread   thread1=new Thread(myThread_02);
        Thread  thread2=new Thread(myThread_02);
        thread1.start();
        thread2.start();
    }
}

3.实现Callable接口创建线程

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

public class MyThread_03 implements Callable<String> {

    @Override
    public String call() throws Exception {
        for (int i = 0; i <3 ; i++) {
            System.out.println(i);
        }
        return "我是Callable,已完成任务";
    }

    public static void main(String[] args)   throws   Exception {
        //利用MyThread类实例化Callable接口的对象
        Callable  callable= new MyThread_03();
        //利用FutureTask类的构造方法public  FutureTask(Claaable<V> callable)
        //将Callable接口的对象传给FutureTask类
        FutureTask task=new FutureTask(callable);
        //将FutureTask类的对象隐式地向上转型
        //从而作为Thread类的public Thread(Runnable runnable)构造方法的参数
        Thread thread=new Thread(task);
        //调用Thread类的start()方法
        thread.start();
        //FutureTask的get()方法用于获取FutureTask的call()方法的返回值,为了取得线程的执行结果
        System.out.println(task.get());

    }

}

运行结果:

对比:

1、继承Thread类有一个缺点就是单继承,而实现Runnable接口则弥补了它的缺点,可以实现多继承

2、继承Thread类必须如果产生Runnable实例对象,就必须产生多个Runnable实例对象,然后再用Thread产生多个线程;而实现Runnable接口,只需要建立一个实现这个类的实例,然后用这一个实例对象产生多个线程。即实现了资源的共享性

3.Runnable接口的run()方法没有返回值,而Callable接口中的call()方法有返回值,若某些线程执行完成后需要一些返回值的时候,就需要用Callable接口创建线程

3.线程的状态转换

从Thread的State中发现线程有六种状态

4.线程的基本操作

4.1.新建线程

新建线程很简单,只要使用new 关键字创建一个线程对象,并将它start启动起来即可。

public class MyThread_04 {
    public static void main(String[] args) throws Exception {
      
        Thread thread = new Thread() {    // 匿名类创建子线程
            @Override
            public void run() {
               for(int i=0;,i<10;i++)
                System.out.println(i);
            }
        };
        
        thread.start();   // 启动线程
}
}

start()方法启动之后调用的是run()方法,但并不能直接调用run方法,因为这样调用的方法会和当前线程方法串行,而不是启动新的线程了。

4.2.终止线程

一般来说,线程在执行完毕后会自动结束,无须手工关闭。但有时有些线程执行本身就是个无穷循环,我们需要手动的对它进行关闭。Thread提供了一个stop()方法,但你会发现方法前加上了@Deprecated,表明这个方法或类不再建议使用。

为什么stop不再建议使用了呢?因为它太暴力了,把执行到一般的线程直接终止,不管线程逻辑是否完整,这是非常危险的.可能会引起一些数据不一样的问题。

public class MyThread_04  {

    public static void main(String[] args) throws Exception {
      
        Thread thread = new Thread() {    // 匿名类创建子线程
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);  // 该线程休眠1秒
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
                System.out.println("此处代码不会执行");
            }
        };
        
        thread.start();   // 启动线程
        Thread.sleep(100);  // 主线程休眠0.1秒
        thread.stop();    // 子线程停止

    }
}

那我们应该如何正确关闭这个线程呢

public class MyTread_05 extends Thread {
    public volatile boolean exit = false;   //定义一个变量来决定线程停止

    @Override
    public void run() {
        while (!exit) {
            System.out.println(1);
        }
    } 
    public static void main(String[] args) throws Exception 
    {
        MyTread_05 thread = new MyTread_05();
        thread.start(); 
        sleep(1000); // 主线程延迟5秒
        thread.exit = true;  // 终止线程thread
        System.out.println("线程退出!"); 
    } 
} 

 

4.3线程休眠(sleep)与中断(interrupt)

Thread.sleep()方法会让当前线程休眠若干时间,它会抛出一个InterruptedException异常。InterruptedException异常不是中断异常,所以必须try-catch处理。当sleep休眠时,如果被中断,这个异常就会产生。

public static native void sleep(long millis) throws InterruptedException:每次休眠多少毫秒
public static void sleep(long millis, int nanos)throws InterruptedException:每次休眠多少毫秒,休眠几次
public class MyThread_8  extends   Thread {
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            try {
                Thread.sleep(1000);//每次休眠一秒
                System.out.println(i);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyThread_8  myThread_8=new MyThread_8();
        myThread_8.start();
    }
}

 

interrupt()不能中断在运行中的线程,它只能改变中断状态而已相关方法是:

public void interrupt() :中断
public static boolean interrupted():判断是否被中断
public boolean isInterrupted():判断是否被中断,并清除当前中断状态

interrupt()方法经常用来吵醒”休眠的线程“.当一些线程调用sleep方法处于休眠状态时,一个占有CPU资源的线程可以调用interrupt方法吵醒自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠

public class MyThread_07{
    public static void main(String[] args) {
        ClaaRoom  claaRoom= new ClaaRoom();

        claaRoom.student.start();
        claaRoom.teacher.start();
    }
}
 class   ClaaRoom implements   Runnable {
    Thread  student,teacher;    //定义两个线程
     public ClaaRoom() {
           student=new Thread(this);
          teacher=new Thread(this);
         teacher.setName("王老师");
         student.setName("小明");
     }
     @Override
     public void run() {
         if (Thread.currentThread() == student) {
             try {
                 System.out.println(student.getName()+"正在睡觉,不听课");
                 Thread.sleep(1000 * 60 * 60);
             } catch (InterruptedException e) {
                 System.out.println(Thread.currentThread().getName() + "被老师吵醒了");
             }
             System.out.println(Thread.currentThread().getName() + "开始听课");
         } else if (Thread.currentThread() == teacher) {
             for (int i = 0; i <= 3; i++) {
                 System.out.println("上课");
             }
             try {
                 Thread.sleep(500);
             } catch (InterruptedException e) { }
             student.interrupt();         //叫醒小明
         }
     }
}

 

4.4等待(wait)和通知(notify)

wait和notify方法并不是Thread里的方法,而是Object类的方法,主要用于多线程间的协同处理,即控制线程之间的等待、通知、切换及唤醒。

在某个线程方法中对wait()和notify()的调用必须指定一个Object对象,而且该线程必须拥有该Object对象的监视器(monitor)。而获取对象监视器(monitor)最简单的办法就是,在对象上使用synchronized关键字。当调用wait()方法以后,该线程会释放掉这个监视器,并进入等待队列中等待。而在其它线程调用这个Object对象的notify()方法时,任选这个对象其中一个等待线程将被唤醒,而notifyAll()则是将其所有等待线程唤醒。

public class MyThread_09 {
    final   static   Object  object= new Object();
    public  static   class   Thread_01 extends   Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(System.currentTimeMillis()+"Thread_01 start");
                try {
                    System.out.println(System.currentTimeMillis()+"Thread_01 wait  for object");
                    object.wait();
                }catch (InterruptedException  e){
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis()+"Thread_01 end");
            }
        }
    }
    public  static   class   Thread_02 extends   Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(System.currentTimeMillis()+"Thread_02 start!  notify  one  Thread");
                object.notify();
                System.out.println(System.currentTimeMillis()+"Thread_02 end");
                try {
                   Thread.sleep(2000);
                }catch (InterruptedException  e){
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread  t1=new Thread_01();
        Thread  t2=new Thread_02();
        t1.start();
        t2.start();
    }
}

 从运行结果来看,线程t2在nitify()方法调用前也必须获得该object对象的监视器,而t1在唤醒后首先不是继续执行下面的代码,而是重新获得object的监视器,如果暂时无法获得及必须等待这个监视器,直到获得监视器才继续往下执行。

注:wait()和sleep()方法都可以让线程等待若干时间,但两者还是有区别的

1)  原理不同。sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,他会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动苏醒。例如,当线程执行报时功能时,每一秒钟打印出一个时间,那么此时就需要在打印方法前面加一个sleep()方法,以便让自己每隔一秒执行一次,该过程如同闹钟一样。而wait()方法是object类的方法,用于线程间通信,这个方法会使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法或者notifyAll()时才醒来,不过开发人员也可以给他指定一个时间,自动醒来。

2)  对锁的 处理机制不同。由于sleep()方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通信,因此,调用sleep()方法并不会释放锁。而wait()方法则不同,当调用wait()方法后,线程会释放掉他所占用的锁,从而使线程所在对象中的其他synchronized数据可以被其他线程使用。

3)  使用区域不同。wait()方法必须放在同步控制方法和同步代码块中使用,sleep()方法则可以放在任何地方使用。sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用他的interrupt(),产生InterruptedException。由于sleep不会释放锁标志,容易导致死锁问题的发生,因此一般情况下,推荐使用wait()方法。

4.5,线程挂起(suspend)和继续执行(resume)线程

线程挂起(suspend)和继续执行(resume)是一对相反的操作,被挂起的线程必须要等到resume()后,才能继续执行。看上出这两个方法应该很好用,但其实他们已经被官方弃用了,不建议使用。原因就是suspend挂起暂停线程的同时,并不会释放任何锁资源,此时,其他任何线程想要访问它占用的锁时都会被牵连,导致无法继续运行。直到对应的线程进行了resume操作,被挂起的线程才可以继续执行。但是resume()操作意外在suspend()前执行了,那么被挂起的线程可能很难有机会继续被执行,被占用的的锁资源不会被释放,导致整个系统工作不正常。而且从它的线程状态来看,还是Runnable,严重影响我们对系统的判断。

public class MyThread_10 {
    public   static  Object  u=new Object();

    static  ChangeObjectThread  t1=new ChangeObjectThread("t1");
    static  ChangeObjectThread  t2=new ChangeObjectThread("t2");

    public  static   class   ChangeObjectThread    extends   Thread{
        public ChangeObjectThread(String  name) {
            super.setName(name);
        }

        @Override
        public void run() {
          synchronized (u){
              System.out.println("in "+ getName());
              Thread.currentThread().suspend();
          }
        }
    }

    public static void main(String[] args)   throws   InterruptedException{
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.resume();
        t2.resume();
    }
}

表明这两个线程先后进入了临界区,但程序不会退出,而是会挂起。 



 4.6 等待线程结束(join)和谦让(yield)

在很多情况下,一个线程的输入可能非常依赖于另外一个或者多个线程的输出,此时,这个依赖的线程就需要等待目标线程执行完毕,才能继续执行.JDK提供了join()方法来实现这个功能

public final void join() throws InterruptedException;//表示无限期等待,它会一直阻塞当前线程,直到目标线程执行完毕
public final synchronized void join(long millis) throws InterruptedException;
//给出了等待时间,如果超过给定时间目标线程还没有执行完毕,当前线程也会因为等待时间过长,开始继续执行
public class MyThread_11 {

        public volatile static int i = 0;
        //目标线程
        public static class AddThread implements Runnable{
            @Override
            public void run() {
                for (i = 0; i < 10;i++){
                    if (i == 9999){
                        System.out.println("自增结束,i = " + i);
                    }
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new AddThread());
            thread.start();
            thread.join();
            System.out.println(i);
        }
    }

从结果来看,主线程是等AddTread线程结束后才执行的,不然打印出来的结果会是下面这样

yield():

public static native void yield(); 
这是一个静态的本地方法,一旦执行,它会使当前线程让出CPU的资源。但是要注意,让出CPU资源并不代表当前线程不执行了。当前线程让出CPU资源后,还会进行CPU资源的争夺,但是能否再次被分配到资源就不一定了。
使用场景:  如果你觉得一个线程不那么重要,或者优先级非常低,并且害怕它占用太多的CPU资源,那么可以在适当的时候调用该方法Thread,yield(),给予其他重要线程更多的执行机会。

5.线程优先级

线程是有优先级的,也就是线程的执行顺序。 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。

获取并设置Java线程的优先级:

public final int getPriority():返回给定线程的优先级。
public final void setPriority(int newPriority):将线程的优先级更改为值newPriority。如果参数
newPriority的值超出最小(1)和最大(10)限制,则此方法抛出IllegalArgumentException。
class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

public class MyThread_12 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThread(), "t1");
        Thread t2 = new Thread(new MyThread(), "t2");
        Thread t3 = new Thread(new MyThread(), "t3");
        t1.setPriority(1);
        t3.setPriority(10);
        t1.start();
        t2.start();
        t3.start();
    }
}

 从结果可知:具有最高优先级的线程将在其他线程之前获得执行机会,优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程

6.守护线程Daemon

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。

守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程(GC)就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了

public class MyThread_13 {
     static class    Daemon  extends  Thread{
        @Override
        public void run() {
            while (true) {
                System.out.println(" daemon  is  alive");
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException  e){
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args)   throws   InterruptedException {
       Thread  thread=new Daemon();
       thread.setDaemon(true);
       thread.start();
       Thread.sleep(3000);
    }
}

从结果可知,在main线程停止后,Daemon线程也停止了,说明Daemon已经设置成了守护线程。需要注意的是:

 1.thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程;2.在Daemon线程中产生的新线程也是Daemon的;3 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值