Java 之多线程

1 概述

  • 进程

进程就是正在运行的程序。进程是系统进行资源分配和调度的独立单位,每个进程都有自己的内存空间和系统资源。

多进程的意义:可以在一个时间段内执行多个任务,提高CPU的使用率。

  • 线程

在同一个进程内可以执行多个任务,而每一个任务都可以看做是一个线程。

线程是程序的执行单元(执行路径),是程序使用CPU的最基本单元

多线程的意义:提高程序的使用率。

因为多个线程共享一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。
在一个时间点上,CPU只能执行一个线程。而线程的执行需要抢占CPU资源,所以线程的运行具有随机性。

  • 并发和并行

并行:逻辑上的同时发生,指在某一个时间段同时运行多个程序

并发:物理上的同时发生,指在某一个时间点同时运行多个程序

2 多线程的常见实现方式

  • 继承 Thread 类

创建继承 Thread 类的自定义类,并重写其 run() 方法,该方法的方法体即为线程执行的内容。接下来创建自定义类的对象,使用对象调用其 start() 方法即可启动线程

public class MyThread extends Thread
{
	@Override
	public void run()
	{
		for(int i = 0; i < 10; i++)
		{
			System.out.println("hello thread");
			try
			{
				Thread.sleep(500);
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

public class ThreadDemo
{
    public static void main(String[] args)
    {
        MyThread myTh = new MyThread();
        myTh.start();
    }
}
  • 实现 Runnable 接口

创建一个实现 Runnable 接口的自定义类,并重写该接口的 run() 方法,该方法的方法体即为线程执行的内容。接下来创建该自定义类的对象,并把这个对象作为参数传递给 Thread 的有参构造函数,生成 Thread 对象,最后调用 Thread 对象的 start() 方法即可启动线程。

public class MyRunnalbe implements Runnable
{
	@Override
	public void run()
	{
		for(int i = 0; i < 10; i++)
		{
			System.out.println("hello runnable");
			try
			{
				Thread.sleep(500);
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

public class ThreadDemo
{
    public static void main(String[] args)
    {
        MyRunnalbe rb = new MyRunnalbe();
        Thread Th = new Thread(rb);
        Th.start();
    }
}
  • 使用线程池

3 Thread 类常见方法

String getName():获取线程名称

void setName(String name):设置线程名称

int getPriority():获取线程优先级

void setPriority(int newPriority):设置线程优先级,范围为 1~10 , 10 的优先级最高,线程优先级越高,获取 CPU 的概率越高。

static void sleep(long millis):线程休眠

final void join():调用该方法的线程执行完毕后其他线程才能执行

static void yield():调用该方法的线程让出 CPU 执行权,所有线程重新抢占 CPU 执行权

void setDaemon():将调用方法的线程设置成守护线程(后台线程),当所有的非守护线程结束时,守护线程自动结束。如果程序运行的线程全都是守护线程,JVM 将会自动退出。

void interrupt():中断线程,原理是终止线程状态,抛出一个InterruptException

4 线程的生命周期

新建:创建线程对象

就绪:线程对象调用 start() 方法,可以执行,但需要抢到 CPU 资源

运行:线程抢到 CPU 资源,执行

阻塞:调用 sleep()、wait() 等操作让线程处于阻塞状态,此时线程不能执行,也不能抢占 CPU;sleep() 时间到、notify() 等操作将让线程脱离阻塞状态,使其处于就绪状态。

死亡:线程对象变成垃圾,等待回收。

5 线程安全

  5.1 出现原因

多线程

● 共享数据

● 多条语句操作共享数据

由于前两点我们无法避免,只能从第三点下手,当一个线程在对共享数据进行操作的时候,其他线程无法进行操作。

  5.2 常用解决办法

● 同步代码块

synchronized(Object lockObj)
{
    需要被同步的代码;
}

注意:同步代码块的锁对象是任意对象,所有线程都应该使用同一个 lockObj,同步虽然解决了线程安全问题,但当线程比较多的时候,每个线程都会去判断同步上的锁,这是很耗费资源的,无形之中降低了程序的运行效率,而且可能出现死锁(同步嵌套时容易出现)。

● 同步方法

权限修饰符 synchronized 返回值类型 方法名(参数列表)
{
    需要被同步的代码;
}

注意:同步方法的锁对象是 this静态同步方法的锁对象是当前类的 Class 对象

● 使用 Lock(接口) 

Lock lock = ReentrantLock();
try
{ 
    lock.lock();
    需要被同步的代码;
}
finally
{
    lock.unlock();
}

● 死锁

当两个或两个以上的线程在执行过程中,因争夺资源产生的一种相互等待的现象。

public class MyLock
{
    public static final Object lockObjA = new Object();
    public static final Object lockObjB = new Object();
}

public class DieLock extends Thread
{
    puoblic boolean flag;

    @Override
    public void run()
    {
        if(flag)
        {
            synchronized(MyLock.lockObjA)
            {
                System.out.println("if lockObjA");
                
                synchronized(MyLock.lockObjB)
                {
                    System.out.println("if lockObjB");
                }
            }
        }
        else
        {
            synchronized(MyLock.lockObjB)
            {
                System.out.println("if lockObjB");
                
                synchronized(MyLock.lockObjA)
                {
                    System.out.println("if lockObjA");
                }
            }
        }
    }
}

public class DieLockDemo
{
    public static void main(String[] args)
    {
        DieLock dl1 = new DieLock();
        DieLock dl2 = new DieLock();

        dl1.flag = true;
        dl2.flga = false;

        dl1.start();
        dl2.start();
    }
}

上面的例程最理想的状态 dl1 完成执行,dl2 再执行,运行结果为 if lockObjA; if lockObjB; else lockObjB; else lockObjA;

不过大多数情况是 dl1 已经获取到 MyLock.lockObjA 即将获取 MyLock.lockObjB,而 dl2 已经获取到 MyLock.lockObjB,即将获取 MyLock.lockObjA;这时 dl1 等待 dl2 释放 MyLock.lockObjB,而 dl2 等待 dl1 释放 MyLock.lockObjA,两个线程相互等待就形成了死锁。

6 线程通信

  6.1 概述

不同种类的线程针对同一个资源的操作(生产、消费者模式)。

  6.2 等待唤醒机制

void notify():唤醒调用方法的锁对象上的单个等待线程

void notifyAll():唤醒调用方法的锁对象上的所有等待线程

void wait():让当前线程进入等待,并立即释放锁,被唤醒后将会继续等待前的状态。

注意:

        以上方法都需要通过锁对象来调用

  6.3 案例

public class Student
{
    public String name;
    public int age;
    public boolean hasData;
}

public class ProducerThread implements Runnable
{
    private Student student;
    private int x = 0;
    
    public ProducerThread(Student student)
    {
        this.student = student;
    }

    @Override
    public void run()
    {
        while(true)
        {
            synchronized(student)
            {
                if(student.hasData)
                {
                    student.wait();
                }
                
                if(x % 2)
                {
                    student.name = "宋子杰";
                    student.age = 18;
                }
                else
                {
                    student.name = "陈静";
                    student.age = 20;
                }

                x++;
                student.hasData = true;
                student.notify();
            }
        }
    }
}

public class ConsumerThread implements Runnable
{
    private Student student;
    
    public ConsumerThread(Student student)
    {
        this.student = student;
    }

    @Override
    public void run()
    {
        while(true)
        {
            synchronized(student)
            {
                if(!student.hasData)
                {
                    student.wait();
                }
                
                System.out.println(s.name + "----" + s.age);
                student.hasData = false;
                student.notify();
            }
        }
    }
}

public class StudentDemo
{
    public static void main(String[] args)
    {
        Student student = new Student();

        ProducerThread pt = new ProducerThread(student);
        ConsumerThread ct = new ConsumerThread(student);
        
        pt.start();
        ct.start();
    }
}

  6.4 线程状态转换

7 线程池

  7.1 概述

每次创建线程都要和系统进行交互,比较耗费资源。而使用线程池,线程结束后不会死亡,而是被线程池回收,达到线程多次使用的目的。

  7.2 使用

● 创建线程池对象

● 定义实现 Runnable 或者 Callable 接口的类

● 调用如下方法

    Future<?> submit(Runnable task)

    <T> Future<T> submit(Callable<T> task)

    调用shutdown()释放线程池

  7.3 案例

public class MyCallable<T> implements Callable<T>
{
    @Override
    public T call() throws Exception
    {
        线程执行的内容;
    }
}

public class CallableDemo
{
    public static void main(String[] args)
    {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(new MyCallable<new Object());
    }
}

使用Runnable实现类和上述代码类似。

注意:

     线程池的线程可以求返回值。

8 线程组

  8.1 概述

java中用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,java允许程序直接对线程组进行控制。

默认情况下,所有的线程都属于主线程组,但也可以显式的指定新建线程的线程组。

final ThreadGroup getThreadGoup():获取调用该方法的线程对象所属的线程组

Thread(ThreadGroup threadGroup,Runnable target,String name):通过构造方法设置线程组

  8.2 用法

ThreadGroup tg = new ThreadGroup("ThreadGroup 1");

Runnable ra = new MyRunnable();

Thread th1 = new Thread(tg, ra, "thread_1");

Thread th2 = new Thread(tg, ra, "thread_2");

tg.destory(); //销毁组内所有线程

tg.setDaemon(true); //将组内所有线程都设置成后台线程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值