【Java】线程

概念

线程与进程

进程

是指一个内存中运行的应用程序,每个进程都有独立的内存空间

线程

是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程

进程像是一个软件,线程是该软件的执行路径。

线程调度

分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),

Java使用的为抢占式调度。

CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时 刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高

同步与异步

同步

排队执行 , 效率低但是安全。

异步

同时执行 , 效率高但是数据不安全。

并发与并行

并发

指两个或多个事件在同一个时间段内发生。

并行

指两个或多个事件在同一时刻发生(同时发生)

并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
它们最关键的点就是:是否同时

如何在Java中实现多线程

线程类 Class Thread

继承Thread

继承Thread的子类就是一个线程类。继承Thread之后,我们需要重写run方法。

run方法:
线程要执行的任务方式。
run方法里的代码是一条新的执行路径
该执行路径的触发方式不是调用run方法,而是同过Thread对象的start()来启动任务。

子线程调用的方法都在子线程中运行。
每个线程都拥有自己的栈空间,公用一份堆内存。

// MyThread.java
public class MyThread extends Thread{
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
                System.out.println("MyThread"+i);
            }
        }
}
// Test.java
public class Test {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main"+i);
        }
    }
}

输出样例:

main0
MyThread0
main1
MyThread1
main2
MyThread2
main3
MyThread3
main4
MyThread4
main5
MyThread5
main6
MyThread6
MyThread7
MyThread8
main7
MyThread9
main8
main9

Thread匿名内部类实现

public class Test {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("匿名内部类实现" + i);
                }
            }
        }.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main"+i);
        }
    }
}

实现Runnable

实现Runnable与继承Thread相比有如下优势:

  1. 通过创建任务,然后给线程分配的方式来实现的多线程.更适合多个线程同时执行相同任务的情况.
  2. 可以避免单继承所带来的局限性.
  3. 任务与线程本身是分离的,提高了程序的健壮性.
  4. 线程池技术,接受Runnable类型的任务,不接收Thread类型的线程.
// MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyRunnable"+i);
        }
    }
}
//Test.java
public class Test {
    public static void main(String[] args) {
    	//1. 创建任务对象
        MyRunnable r = new MyRunnable();
        //2. 创建一个线程,并为其分配一个任务
        Thread t = new Thread(r);
        //3. 执行这个线程
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main"+i);
        }
    }
}

Class Thread

常用构造方法

ConstructorDescription
Thread()Allocates a new Thread object.
Thread(Runnable target)Allocates a new Thread object o that it has target as its run object.
Thread(Runnable target, String name)Allocates a new Thread object so that it has target as its run object, has the specified name as its name.
Thread(String name)Allocates a new Thread object has the specified name as its name.

常用方法

Modifier and TypeMethodDescription
longgetId()Returns the identifier of this Thread.
StringgetName()Returns this thread’s name.
intgetPriority()Returns this thread’s priority.
voidsetPriority(int newPriority)Changes the priority of this thread. (Parameters: MIN_PRIORITY, NORM_PRIORITY, MAX_PRIORITY)
voidstart()Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
voidstop()This method is utdated and unsafe. Don’t use.
static voidsleep(long millis)Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.
static voidsleep(long millis, int nanos)Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds plus the specified number of nanoseconds, subject to the precision and accuracy of system timers and schedulers.
voidsetDaemon(boolean on)Marks this thread as either a daemon thread or a user thread.

Daemon threads:
线程分为守护线程(守护用户线程)和用户线程
所有用户线程全部结束,它们的守护线程才会结束结束。

  1. getName() :

    Example: Get the name of current thread
    System.out.println(Thread.currentThread().getName());

  2. sleep() :

    Example: Sleep for a second
    Thread.sleep(1000);

补充

线程堵塞和中断

线程堵塞

所有比较消耗时间的操作。

线程中断

一个线程的是一个独立的执行路径,它是否应该结束由其自身决定。

public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 给线程t1添加中断标记
        t1.interrupt();

    }
    
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // 中断标记只会进入catch块
                    //e.printStackTrace();
                    // 如果没有死亡操作,线程不会死亡
                    return; //return结束线程
                }
            }
        }
    }
}

线程不安全

多个线程同时执行(争抢)同一个数据时,线程不安全。

如何处理线程不安全问题

要让线程排队执行,加锁执行。有以下三种方法:

方法格式描述
同步代码块(关键字)synchronized(锁对象){}隐式锁,多个线程的锁对象必须唯一
同步方法(修饰符)synchronized 返回类型 方法名(){}隐式锁,谁调用该方法谁就是锁对象
显示锁ReentrantLock 类的 lock()/unlock() 方法显式锁,有程序员决定在那开启/关闭锁
方法一:同步代码块(关键字)

格式: synchronized(同一个锁对象){ }

public class Test {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
    
    static class Ticket implements Runnable{
        private int count = 10;
        private Object o = new Object();
        
        @Override
        public void run() {
            while(true) {
                synchronized (o) {
                    if(count>0) {
                        System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                    }
                    else break;
                }
            }
        }
    }
}
方法二:同步方法(修饰符)

以下两种方法是等价的:

public synchronized void sale() { // 锁住this
    count --;
} // 解锁
 public void sale() {
    synchronized(this) { // 锁住this
        count --;
    } // 解锁
} 

对于static方法,是没有this实例的,因为static方法是针对类而不是实例。但是我们注意到任何一个类都有一个由JVM自动创建的Class实例,因此,对static方法添加synchronized,锁住的是该类的Class实例。

public class Test{
    public static void sale() {
        synchronized(Test.class) {
            ...
        }
    }
}

测试代码:

import java.util.TimerTask;

public class Test {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        private int count = 10;
        private Object o = new Object();
        @Override
        public void run() {
            while(true) {
                boolean flag = sale();
                if(!flag) break;
            }
        }

        public synchronized boolean sale(){
            if(count>0) {
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                return true;
            }
            return false;
        }
    }
}
方法三:显示锁 Lock 子类 ReentrantLock
public class Test {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        private int count = 10;
        private Lock l = new ReentrantLock();
        @Override
        public void run() {
            while(true) {
                l.lock();
                if(count>0) {
                    System.out.println("正在准备卖票");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                }
                else break;
                l.unlock();
            }
        }
    }
}

公平锁和非公平锁

公平锁

先到先得

用法:显示锁 l:fair 参数为 true 表示公平锁
Lock l = new ReentrantLock(true);

非公平锁

(一起解锁,然后)同时抢

线程死锁

public void add(int m) {
    synchronized(lockA) { // 获得lockA的锁
        this.value += m;
        synchronized(lockB) { // 获得lockB的锁
            this.another += m;
        } // 释放lockB的锁
    } // 释放lockA的锁
}

public void dec(int m) {
    synchronized(lockB) { // 获得lockB的锁
        this.another -= m;
        synchronized(lockA) { // 获得lockA的锁
            this.value -= m;
        } // 释放lockA的锁
    } // 释放lockB的锁
}

当两个线程分别执行add()和dec()方法时:

  • 线程1:进入add(),获得lockA;
  • 线程2:进入dec(),获得lockB。

随后:

  • 线程1:准备获得lockB,失败,等待中;
  • 线程2:准备获得lockA,失败,等待中。

此时,两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。

死锁发生后,没有任何机制能解除死锁,只能强制结束JVM进程。

多线程通信问题

多线程通信问题也可以理解为生产者与消费者的问题,多线程互交。

生产者在生产时,消费者没有在消费
消费者在消费时,生产者没有在生产

Object类的方法:

Modifier and TypeMethodDescription
voidnotify()Wakes up a single thread that is waiting on this object’s monitor.
voidnotifyAll()Wakes up all threads that are waiting on this object’s monitor.
voidwait()Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.

this.notifyAll();
this.wait();

线程的六种状态


new:线程创建,未启动
Waiting:无限等待

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值