Java基础之多线程

本文深入探讨Java多线程,包括线程同步、Lock锁、生产者消费者模式、阻塞队列、线程池、Volatile关键字以及Atomic包等核心概念。详细介绍了各种线程同步机制和并发工具类的使用,帮助理解Java并发编程的关键知识点。

多线程

实现多线程方式一: 继承Thread类

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(i);
        }
    }
}
//=============================================
public class Test1 {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.start();
        mt2.start();
    }
}

注意
1、为什么要重写run()方法
因为run()是用来封装杯线程执行的代码

2、run方法和start()方法的区别
run():封装线程执行的代码,直接调用,相当于普通方法调用
start():启动线程;由JVM调用此线程的run()方法

实现多线程方式二:  实现Runnable接口

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

//=================================

public class Test1 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.start();
        t2.start();
    }
}

实现多线程方式二:  实现Callable接口

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i <= 10; i++) {
            System.out.println(i);
        }
        return "好的";
    }
}

public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<String>(myCallable);
        Thread t1 = new Thread(futureTask);
        t1.start();
        String s = futureTask.get();
        System.out.println(s);
    }
}

Thread构造方法:

方法名说明
Thread(Runnable target)分配一个新的Thread对象
Thread(Runnable target, String name)分配一个新的Thread对象

Thread常用方法:

方法名说明
void setName(String name)将此线程的名称更改为等于参数name
String getName()返回此线程的名称
Thread currentThread()返回对当前正在执行的线程对象的引用

public class MyThread extends Thread {
    public MyThread() {}

    public MyThread(String name) {
        super(name);
    }

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


public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread("小红");
        myThread1.setName("小明");
        myThread1.start();
        myThread2.start();
        System.out.println(Thread.currentThread().getName());
    }
}

线程休眠

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.start();
        t2.start();
    }
}

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

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

注意:
Java使用的是抢占式调度模型

相关方法

方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}



public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        System.out.println(t1.getPriority());
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}

守护线程

守护线程是一种特殊的线程。当进程中不存在非守护线程时,则守护线程自动销毁

方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

线程同步

同步代码块格式:

synchronized(任意对象) { 
	多条语句操作共享数据的代码 
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步方法的格式:

修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

同步方法的锁对象一般是: this

静态同步方法:

修饰符 static synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

同步静态方法锁对象一般是  类名.class

Lock锁

Lock是接口不能直接实例化,可以用它的实现类ReentrantLock来实例化

构造方法:

方法名说明
ReentrantLock()创建一个ReentrantLock的实例

加锁解锁方法

方法名说明
void lock()获得锁
void unlock()释放锁

生产者和消费者模式

生产者消费者模式是一个十分经典的多线程协作的模式,
一类是生产者线程用于生产数据
​一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

Object类的等待和唤醒方法

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

阻塞队列

在这里插入图片描述
常见BlockingQueue:
ArrayBlockingQueue: 底层是数组,有界
LinkedBlockingQueue: 底层是链表,无界(最大为int的最大值)

BlockingQueue的核心方法:
put(anObject): 将参数放入队列,如果放不进去会阻塞
take(): 取出第一个数据,取不到会阻塞


线程状态

线程状态具体含义
NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
RUNNABLE当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态

线程池

线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。

Executors默认线程池

// 创建一个默认的线程池
static ExecutorService newCachedThreadPool()  

//创建一个指定最多线程数量的线程池
​static newFixedThreadPool(int nThreads)	    
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        //使用Executors的静态方法创建线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(()-> System.out.println(Thread.currentThread().getName()));
        executorService.submit(()-> System.out.println(Thread.currentThread().getName()));
        //关闭线程池
        executorService.shutdown();
        Thread.sleep(300);
        System.out.println("===============");

        //使用Executors的静态方法创建线程池(指定最多线程数量的线程池)
        //最大值是10,但不是初始值
        ExecutorService executorService1 = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService1;

        //看看线程池中 线程数  0
        System.out.println(pool.getPoolSize());
        executorService1.submit(()-> System.out.println(Thread.currentThread().getName()));
        executorService1.submit(()-> System.out.println(Thread.currentThread().getName()));

        //此时数量为2
        System.out.println(pool.getPoolSize());

        //关闭线程池
        executorService1.shutdown();
    }
}

Volatile关键字

强制线程每次在使用的时候,都会看一下共享区域最新的值

原子性Atomic包

java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)

例:AtomicInteger: 原子更新整型

public AtomicInteger():	   			  	初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue):  	初始化一个指定值的原子型Integer

int get():   			 				 获取值
int getAndIncrement():      			 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet():     				 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data):				 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value):   			 以原子方式设置为newValue的值,并返回旧值。

AtomicInteger原理 : 自旋锁 + CAS 算法

有3个操作数(内存值V, 旧的预期值A,要修改的值B)

当旧的预期值A == 内存值   此时修改成功,将V改为B

当旧的预期值A!=内存值   此时修改失败,不做任何操作

并重新获取现在的最新值(这个重新获取的动作就是自旋)

悲观锁和乐观锁

synchronized和CAS的区别 :

相同点:在多线程情况下,都可以保证共享数据的安全性。

不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)

cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
  如果别人修改过,那么我再次获取现在最新的值。
  如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

并发工具类

HashMap是线程不安全的。多线程环境下会有数据安全问题

Hashtable是线程安全的,但是会将整张表锁起来,效率低下

ConcurrentHashMap也是线程安全的,效率较高。
在JDK7和JDK8中,底层原理不一样。

public class MyConcurrentHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 25; i++) {
                hm.put(i + "", i + "");
            }
        });


        Thread t2 = new Thread(() -> {
            for (int i = 25; i < 51; i++) {
                hm.put(i + "", i + "");
            }
        });

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

        System.out.println("----------------------------");
        //为了t1和t2能把数据全部添加完毕
        Thread.sleep(1000);

        //0-0 1-1 ..... 50- 50

        for (int i = 0; i < 51; i++) {
            System.out.println(hm.get(i + ""));
        }//0 1 2 3 .... 50
    }
}

CountDownLatch类

方法解释
public CountDownLatch(int count)参数传递线程数,表示等待线程数量
public void await()让线程等待
public void countDown()当前线程执行完毕

Semaphore类

可以控制访问特定资源的线程数量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值