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); //将组内所有线程都设置成后台线程