java多线程并发和锁机制


线程和进程:

什么是线程,什么是进程?

  • 作业(JOB)计算机的一次执行任务,对于单核CPU来说,作业就是线程为了完成某一个任务所占用一段时间的震荡时钟。
  • 线程(Thread)是计算机执行计划任务的最小单元(携带了很多的作业(JOB)),线程中的作业基本上都是同步的,如果是异步,那么就会大大降低应用程序的效率。
  • 进程(Process)很多线程的集合,进程在操作系统级别是管理线程的工具,也就是说,一个进程里面有很多个线程。线程的名字,我们可以自己给出,但是进程必须是由操作系统,给出一个独有的进程号,这个有进程号的进程就是一系列线程任务的容器。

一个进程就是一个应用程序,其实这种说法也对也不对,因为很多人就是这样认知的,有的应用程序就是一个进程独立完成所有任务,但是有的应用程序会启动多个进程(将不同的任务点分配给不同的进程)。

当我们在java中启动一个main方法,其实就是启动了一个进程,而这个进程中只有一个叫main的主线程,我们真正启动的时候,就是在黑框启动了一个java.exe的程序,这个程序中有且只有一个名叫main的线程。

在这里插入图片描述
一个线程能控制其他线程的启动和结束,就叫做主线线程。


线程的创建和启动:

1、继承Thread

启动方法:start()方法

作业方法:run()方法

  • 执行strat()和调用run()的区别:执行strat()方法就是启动子线程,让子线程自己执行自己的run()方法,这就是多线程启动,但是直接执行run()方法却是用main方法调用了子线程的run()方法,只有一个线程(主线程main)在工作。

2、实现Runnable接口

  • Runnable接口只有一个方法(run()),所以你可以理解为它就是个任务,他并不是个线程,最终启动还是要用Thread的start()方法来启动子线程,就是和你要在Thread里面重写的run()方法一样。
  • 启动该线程的时候的写法:在Thread的构造方法中将实现Runnable的类对象传进去,最后调用start()方法启动线程。
  • 这种创建线程的好处就是将继承的能力留给更重要的类去使用,因为java是单继承多实现的,所以用接口创建线程还是很有好处的。

3、实现Callable接口,有返回值的线程

  • 重写接口里面的call()方法,其中Callable接口中只有一个call()方法,call()方法只是提供了一个返回值,此时需要一个任务来包裹这个返回值,java中提供了这个实现类就是FutureTask,FutureTask在源码中实现了RunnableFuture接口,这个接口它继承了Runnable接口和Future接口,当我们在执行Runnable接口中的run()方法的时候,将Callable中的返回值设置成为一个属性,然后可以通过调用get()方法取出这个属性,最后在main()方法中执行和Runnnable接口创建线程一样的过程来启动线程,从而实现了这种有返回值的创建线程的过程。

4、线程池

Java通过Executors提供四种线程池,分别为:

1、newSingleThreadExecutor

  • 创建一个单线程的线程池,它只会用一个工作线程来执行任务,保证所有任务按照指定顺序执行。

2、newFixedThreadPool

  • 创建一个固定线程数的线程池,可控制线程最大并发数,超出的线程会在队列中等待。

3、newScheduledThreadPool

  • 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。

4、newCachedThreadPoo

  • 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。


线程常用的方法:

currentThread():静态方法,返回当前正在执行的线程对象的引用

sleep():静态方法,当前线程睡眠,参数是毫秒级别

wait():Object的方法,设置当前线程等待,调用哪个对象的wait()方法,就要给哪个对象加锁,调用同一个对象的notify()方法,可以随机唤醒调用同一个对象wait()方法的线程,注意是随机的,调用notifyAll()方法,唤醒所有调用wait()的线程。


java中线程的6中状态:


在这里插入图片描述


  • 创建(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态的称为“运行”,线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  • 阻塞(BLOCKED):比如当前线程可能没有抢到同步锁,或者线程需要IO动作所以被阻塞,调用sleep()方法进入阻塞状态。
  • 等待(WAITING):调用wait()方法,没有参数此时只能用notify()方法唤醒。
  • 超时等待(TIMED_WAITING):调用wait()方法设置参数,在参数规定的时间中等待。
  • 终止(TERMINATED):表示该线程已经执行完毕。


sleep()和wait()方法的区别:

1、wait()方法是Object的方法。

​ sleep()方法是Thread的方法。

2、wait()方法是只能在同步块中使用。

​ sleep()方法是可以在任何场景下使用。

3、wait()方法可以随时唤醒。

​ sleep()方法只能睡够才可以继续运行。

4、wait()方法会释放锁资源

​ sleep()方法不会释放锁资源。


线程加锁后为什么就会同步安全了?

当一个进程在cpu下的时候,该进程下的所有线程就会在这个CPU下执行。此时对于这个进程下所有线程来说,就是单一时钟在震荡,就算两个线程同时到达,CPU也只会选择其中一个线程去执行,所以给线程加锁是安全的,不用担心两个线程同时到达同时执行,因为单一震荡时钟的CPU在同一时刻下,只能执行一个线程,所以带锁的线程抢占后立刻加锁,后面的线程要操作加锁的对象的时候,只能被阻塞。


线程的锁机制:

synchronized:重量锁,内容以一个对象或一组类型。当某一个线程率先抢占到锁的对象的时候,以后别的线程就不可以修改这个对象的内容。

synchronized可以锁住的东西(共用的资源)

  • 共用的对象:多个线程操作同一对象的时候
  • 共用的类型:当多个线程操作统一种类型的对象的时候
  • 共用的方法:synchronized可以锁住多个线程将要共用的方法

synchronized解析:

  • 同步锁,当多个线程要操作同一对象(同一数据)的时候,锁住的其实是对象,其他没抢到锁的线程也就操作不了共享对象了,然后没抢到锁的线程被阻塞,所以多线程下才能实现同步执行。代码层面理解是必须要有共享对象,不然synchronized不可能实现多线程的同步。
  • 当有多个共享对象的时候,synchronized是锁不住的,因为它只能锁住单一对象,所以并不灵活。

共享数据类:

//共享数据
public class ObjectTest {

    public void foo() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }

}

线程:

public class ThreadTest implements Runnable {

//    设置共享数据,保证多线程操作的同一对象
    private ObjectTest test;

    public ThreadTest(ObjectTest test) {
        this.test = test;
    }

    @Override
    public void run() {
//        锁住共享对象
        synchronized (test) {
            test.foo();
        }
    }

}

主方法启动:

public class Main {

    public static void main(String[] args) {
        ObjectTest test = new ObjectTest();
        Thread thread0 = new Thread(new ThreadTest(test), "线程1");
        Thread thread1 = new Thread(new ThreadTest(test), "线程2");

        thread0.start();
        thread1.start();
    }
}

结果打印:

在这里插入图片描述



ReentrantLock常用方法:

  • lock():开启锁
  • unlock():释放锁

ReentrantLock(重入锁): java.util.concurrent线程并发包下的类,就只为了实现线程同步,再没有共享对象的情况下或者共享对象很多的情况下实现同步过程。

  • 重入锁,当要操作多个共享数据的时候,lock锁很好用,它不用锁住共享对象,其他线程只要拿到同一个lock,那么就会实现多线程同步执行。
  • 重入锁最大的意义是可以重复加锁,但是要注意释放,要不然当前线程执行完,其他线程就会一直等待。

线程:

import java.util.concurrent.locks.ReentrantLock;

public class LockTest implements Runnable{

//    传入lock锁
    private ReentrantLock lock;

    public LockTest(ReentrantLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
//        lock加锁,注意这里加锁根本没有共享对象
        lock.lock();
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
//        lock释放锁
        lock.unlock();
    }
}

主方法启动:

import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread thread0 = new Thread(new LockTest(lock), "lock");
        Thread thread1 = new Thread(new LockTest(lock), "test");

        thread0.start();
        thread1.start();
    }
}

结果打印:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值