Java---线程(Thread)

程序,进程,线程

程序: 静态的代码 在电脑上安装的静态的代码文件

进程: 运行中的程序--进程, 是操作系统进行资源分配的最小单位

线程: 进程中的一个执行单元(执行任务), 是CPU执行的最小单位。 一个线程,就是一个独立的任务

进程和线程之间的关系:

进程大,线程小

线程属于进程的管理。一个进程内,至少有一个线程,即主线程

线程不能脱离于进程而存在。

java中创建线程

方式1: 继承Thread类

在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法。 Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行run方法。

步骤:

  1. 定义一个子类MyThread继承线程类java.lang.Thread,

  1. 重写run()方法

  1. 创建MyThread类的对象

  1. 调用线程对象的start()方法启动线程(启动后还是执行run方法的)

优缺点:

  • 优点:编码简单

  • 缺点:线程类已经继承Thread,无法继承其他类,不利于扩展

/*
MyThread extends Thread
   我们的类,就是一个线程类,重写run()方法,  需要独立在线程中执行的任务,写在run();
 */
public class MyThread  extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            System.out.println("MyThread:"+i);
        }
    }
}
public class Test {
    //main方法  ----> 主线程
    public static void main(String[] args) {
        System.out.println("main开始执行");
        //创建自定义的线程对象
        MyThread myThread = new MyThread();
        //myThread.run();//这是普通的方法调用,没有启动线程,不是独立的任务
        myThread.start();//启动线程任务,就是独立的,与main可以同时执行的

        for (int i = 0; i <1000 ; i++) {
            System.out.println("main:"+i);
        }
        System.out.println("main结束执行");
    }
}

1、为什么不直接调用了run方法,而是调用start启动线程?

直接调用run方法会当成普通方法执行,此时相当于还是单线程执行,只有调用start方法才是启动一个新的线程执行

2、不要把主线程任务放在子线程之前了。

这样主线程一直是先跑完的,相当于是一个单线程的效果了。

方式2.实现Runnable接口

步骤:

  1. 定义一个线程任务类MyThread实现Runnable接口,重写run方法

  1. 创建MyThread任务对象

  1. 国把MyThread任务对象交给Thread处理。

  1. 调用线程对象的start()方法启动线程

优缺点:

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强

缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的

public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("run:"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();//创建线程的执行任务,并没有真正的创建线程
        Thread thread = new Thread(myThread);//创建一个线程,向线程中添加执行的任务
        thread.start();//启动线程
        for (int i = 0; i < 100; i++) {
            System.out.println("main:"+i);
        }
    }
}

多线程的实现方案二:实现Runnable接口(匿名内部类形式)

  1. 可以创建Runnable的匿名内部类对象

  1. 交给Thread处理。

  1. 调用线程对象的start()启动线程

方式3:JDK5.0新增,实现Callable接口

前2种线程创建方式都存在一个问题:他们重写的run方法均不能直接返回结果,不适合需要返回线程执行结果的业务场景。

怎么解决这个问题呢? JDK 5.0提供了Callable接口和FutureTask类来实现。

这种方式的优点是:

  • 相比run()方法,可以有返回值 ,支持泛型的返回值

  • 方法可以抛出异常

接收任务:FutureTask futureTask = new FutureTask(任务);

创建线程:Thread t = new Thread(futureTask);

启动线程:t.start();

Integer val = futureTask.get();获得线程call方法的返回值

import java.util.concurrent.Callable;
//public class Sum<V> implements Callable<V>//子类也为泛型
public class Sum implements Callable<Integer> {//父类明确类型
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 1; i <=100 ; i++) {
            sum+=i;
        }
        return sum;
    }
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestSum {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Sum sum=new Sum();
        FutureTask<Integer> futureTask=new FutureTask(sum);
        Thread thread=new Thread(futureTask);//借助FutureTask类,添加任务到线程中
        thread.start();
        Integer s=futureTask.get();//获取线程中的返回值
        System.out.println(s);
    }
}

Thread类中方法

方法

说明

run();

定义线程执行的任务

start();

启动线程 private native void start0()

Thread(Runnable target)

为线程添加任务

Thread(String name)

为线程设置名字

Thread(Runnable target, String name)

添加任务,名字

static Thread currentThread()

返回对当前正在执行的线程

void setName(String name)

将此线程的名称更改为等于参数 name 。

String getName()

获取线程的名称

void setPriority(int newPriority)

更改此线程的优先级。

int getPriority()

返回此线程的优先级。

native修饰的方法没有方法体,表示是本地方法,此方法不是java实现的,是操作系统实现的

public class MyThread extends Thread{
    /*public MyThread(String name) {//有参构造方法
        super(name);
    }*/

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.setName("兔子");//设置名字
        myThread1.setPriority(10);//设置优先级
        myThread1.start();//启动线程

        MyThread myThread2 = new MyThread();
        myThread2.setName("乌龟");
        myThread2.setPriority(1);
        myThread2.start();

        // new Thread(Runnable target);
        // new Thread(name)
        // new Thread(Runnable target, String name)
 System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());//main
    }
}

线程优先级

事实上,计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;

优先级较高的线程有更多获得CPU的机会,反之亦然;

优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级;

调度策略 :

  • 时间片

  • 抢占式:高优先级的线程抢占CPU

Java的调度方法 :

  • 同优先级线程组成先进先出队列,使用时间片策略

  • 对高优先级,使用优先调度的抢占式策略

Thread类有如下3个静态常量来表示优先级:

  • static int MAX_PRIORITY:取值为10,表示最高优先级。

  • static int MIN_PRIORITY:取值为1,表示最底优先级。

  • static int NORM_PRIORITY:取值为5,表示默认的优先级。

线程的状态

线程在它的生命周期中会处于不同的状态:

线程的状态:

  • 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源

  • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能

  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态

  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

方法

说明

void stop()

线程停止

static void yield()

线程主动让步

static void sleep(long millis)

执行的线程以指定的毫秒数暂停(暂时停止执行),

static void sleep(long millis,int nanos)

以指定的毫秒数加上指定的纳秒数来暂停

void join()

等待这个线程死亡(等当前线程执行完之后,再执行其他线程)

void join(long millis)

等待这个线程死亡最多 millis毫秒

void join(long millis,int nanos)

等待最多 millis毫秒加上 nanos纳秒这个线程死亡

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            /*if(i%10==0){
                Thread.yield();//线程主动让步,主动失去CPU执行权,回到就绪状态
            }*/

            /*try {
                Thread.sleep(2000);//执行的线程以指定的毫秒数暂停
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/     System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread1 = new MyThread();
        myThread1.setName("窗口1");
        myThread1.start();
        myThread1.join();//等待这个线程运行完,再执行其他线程

        MyThread myThread2=new MyThread();
        myThread2.setName("窗口2");
        myThread2.start();			       System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
    }
}

龟兔赛跑

package com.ffyc.javathread.hm;

public class Game implements Runnable{
    boolean iswin=true;//获胜的一个标志
    @Override
    public void run() {
        for (int i = 1; i <= 1000; i++) {
            if(Thread.currentThread().getName().equals("兔子")){
                System.out.println("兔子跑了"+i+"步");
                if(i==500){
                    try {
                        Thread.sleep(20000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            if(Thread.currentThread().getName().equals("乌龟")){
                System.out.println("乌龟跑了"+i+"步");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(i==1000){
                if(iswin){
                    System.out.println(Thread.currentThread().getName()+"获胜了");
                    iswin=false;
                }
            }
        }
    }
}
package com.ffyc.javathread.hm;

public class TestGame {
    public static void main(String[] args) {
        Game game = new Game();
        Thread tz = new Thread(game, "兔子");
        Thread wg = new Thread(game, "乌龟");
        tz.start();
        wg.start();
    }
}

守护线程

Java中的线程分为两类:用户线程和守护线程

任何一个守护线程都是整个JVM中所有非守护线程的保姆,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;

只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC(垃圾回收器),它就是一个很称职的守护者。

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了.

设置守护线程: setDaemon(boolean on)

注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。

public class ThreadDemo extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程"+i);
        }
    }
}
public class DaemonDemo extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("守护线程");
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        ThreadDemo t1=new ThreadDemo();
        t1.start();

        DaemonDemo daemonDemo=new DaemonDemo();
        daemonDemo.setDaemon(true);//在线程启动前,设置线程为守护线程。当其他的用户线程执行结束后,守护线程也就会销毁
        daemonDemo.start();
    }
}

多线程概念

多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

何时需要多线程

  • 程序需要同时执行两个或多个任务。

  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

多线程的优点

  • 提高程序的响应.

  • 提高CPU的利用率.

  • 改善程序结构,将复杂任务分为多个线程,独立运行

多线程的缺点

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多。(通过提升硬件配置解决)

  • 多线程需要协调和管理,所以需要CPU时间跟踪线程。(通过提升硬件配置解决)

  • 线程之间对共享资源的访问会相互影响;(出现条件:首先是多个线程情况下,其次是多个线程需要操作同一个资源(变量)。案例: 龟兔赛跑 卖票 取款)

线程同步

并发与并行

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

并发:在一个时间段内依次执行操作.例如卖票,抢购,秒杀看似同时进行,实际是一个一个执行

多线程同步 :多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到;

同步就是排队+锁

● 几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作;

● 为了保证数据在方法中被访问时的正确性,在访问时加入锁机制

多个线程访问同一个共享的数据.如果不加以控制,在理论上可能会出现问题。确保一个时间点只有一个线程访问共享资源,可以给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源。

(1)synchronized(同步锁)

1.使用synchronized(同步锁)关键字同步代码块。

synchronized (锁对象){
// 需要被同步的代码;
}

2.synchronized还可以放在方法声明中,表示整个方法,为同步方法。

例如:

public synchronized void show (String name){
// 需要被同步的代码;
}

同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用来充当锁标记)。使用该对象来记录有没有线程进入到同步代码块中。该对象的对象头中的一块空间来记录锁的状态。

同步执行过程 :

1.第一个线程访问,锁定同步对象,执行其中代码.

2.第二个线程访问,发现同步对象被锁定,无法访问.

3.第一个线程访问完毕,解锁同步对象.

4.第二个线程访问,发现同步对象没有锁,然后锁定并访问.

模拟卖票 ,两个窗口分别售票,票数为10张:

1.使用继承Thread方式,synchronized修饰一段代码块:

public class TicketThread extends Thread{
    static int num=10;//static修饰后,变成了静态变量,在内存中只有一份了,两个线程对象就共用同一个
    static Object s=new Object();//锁对象:必须确保多个线程对应的是同一个对象,记录有无线程进入同步代码块中

    @Override
    public void run() {
        while(true){
            synchronized (s){
                if(num>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"买到了:"+num--);
                }else{
                    break;
                }
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        TicketThread t1=new TicketThread();//num=10
        t1.setName("窗口1");
        t1.start();

        TicketThread t2=new TicketThread();
        t2.setName("窗口2");
        t2.start();
    }
}

2.使用继承Thread方式,synchronized修饰方法:

package com.ffyc.javathread.demo5;

public class TicketThread extends Thread{
    static int num=10;
    @Override
    public void run() {
        while(true){
            if(num<=0){
                break;
            }
            try {
                TicketThread.printTicket();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
   /*
       synchronized修饰非static方法时, 锁对象是this,有可能有多个this.
       static synchronized  修饰的是静态方法时,此时锁对象变为了该类的Class类的对象.
     */
    public static synchronized void printTicket() throws InterruptedException {
        if(num>0){
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"买到了"+num--);
        }
    }
}

3.实现Runnable方式,synchronized修饰一段代码块:

public class Ticket implements Runnable{
    int num=10;//对于多个线程来说,票数只有一份
    @Override
    public void run() {
        while(true){
            synchronized (this){//this只有一个,Ticket对象只被创建了一次
                if(num>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"买到了"+num--);
                }else{
                    break;
                }
            }
        }
    }
}
package com.ffyc.javathread.demo5;

public class Test2 {
    public static void main(String[] args) {
        Ticket t=new Ticket();//创建了一个出票的任务
        Thread thread1=new Thread(t,"窗口1");
        Thread thread2=new Thread(t,"窗口2");
        thread1.start();
        thread2.start();
    }
}

4.实现Runnable方式,synchronized修饰方法:

public class Ticket implements Runnable{
    int num = 10;//对于多个线程来说,票数只有一份
    @Override
    public void run() {
        while(true){
             if(num<=0){
                 break;
             }
             this.printTicket();
        }
    }

    public  synchronized   void printTicket(){
        if(num>0){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"买到了:"+num--);
        }
    }
}

(2)Lock(锁)

从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁

package com.ffyc.javathread.demo6;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable{
    int num=10;
    Lock lock=new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try {
                lock.lock();//加锁
                if(num>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"买到了"+num--);
                }else {
                    break;
                }
            }finally {
                lock.unlock();//释放锁(如果在释放锁之前可能出现异常的话,应该把释放锁写在finally里面)
            }
        }
    }
}
package com.ffyc.javathread.demo6;

public class TestTicket {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        Thread thread1=new Thread(ticket,"窗口1");
        Thread thread2=new Thread(ticket,"窗口2");
        thread1.start();
        thread2.start();
    }
}

synchronized锁 和 Lock锁区别:

1.底层实现原理不同

  • synchronized是关键字,底层实现不依赖与java代码,依赖于底层指令

  • Lock锁底层实现,完全是java代码进行控制的

2.用法不同

  • synchronized修饰代码块, 也可以修饰方法

  • Lock锁只能对某一段代码进行加锁

3.加锁和释放锁的方式不同

  • synchronized加锁,释放锁是隐式的

  • Lock锁加锁和释放锁都是显示的

线程死锁

多个线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

案例:中国人和美国人吃饭

正常情况: 中国人:两个筷子 美国人:刀和叉

特殊情况: 中国人: 一个筷子 一把刀 美国人:一个筷子 一把叉(容易发生死锁)

设计时考虑清楚锁的顺序,尽量减少嵌套的加锁交互数量。

package com.ffyc.javathread.demo6;
//多个线程分别占用对方需要的同步对象不放手程序也不会报错,也不提示,相互等待对方释放锁.称为死锁
public class DieLock extends Thread{
    //模拟两把锁
    static Object objectA=new Object();
    static Object objectB=new Object();
    boolean flag;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (objectA){
                System.out.println("if objectA");
                synchronized (objectB){
                    System.out.println("if objectB");
                }
            }
        }else{
            synchronized (objectB){
                System.out.println("else objectB");
                synchronized (objectA){
                    System.out.println("else objectA");
                }
            }
        }
    }
}
package com.ffyc.javathread.demo6;

public class TestDieLock {
    public static void main(String[] args) {
        DieLock dieLock1=new DieLock(true);
        DieLock dieLock2=new DieLock(false);
        dieLock1.start();
        dieLock2.start();
    }
}
/*可能会出现死锁,可能不会出现死锁
if objectA
else objectB
*/

线程通信

线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。

涉及三个方法:

  • wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。

  • notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,唤醒优先级高的那个。

  • notifyAll一旦执行此方法,就会唤醒所有被wait的线程。

注意:wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

两个线程交替打印1-100之间的数字

public class PrintMunber implements Runnable{
    int num=0;
    //wait(); 在Object类中定义的,表示让线程等待,必须通过notify()进行唤醒,线程等待时,可以自动释放锁对象
    //notify(); 在object类中定义的,唤醒等待状态的线程
    //notifyALL(); 唤醒所有等待状态的线程
    @Override
    public void run() {
        while(true){
            synchronized (this){
                this.notify();//唤醒等待的线程
                if(num<=100){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+num++);
                }else{
                    break;
                }
                try {
                    this.wait();//让线程进入等待,把锁释放。然后线程2进来拿到锁,把1唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class TestPrintMunber {
    public static void main(String[] args) {
        PrintMunber p=new PrintMunber();//创建一个打印数字的任务
        Thread thread1=new Thread(p,"线程1");
        Thread thread2=new Thread(p,"线程2");
        thread1.start();
        thread2.start();
    }
}

wait和sleep区别:

sleep 休眠一段时间后自己醒来

wait 等待,等待后不能自己醒来,需要另一个线程唤醒。wait后就会把锁释放掉,另一个线程就获取锁 唤醒等待的线程

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值