Java多线程线程学习
一、进程与线程
1、什么是进程?
进程是静态程序在信息处理过程中的一个动态实体,包含了所有变量和其他状态等。在多任务的操作系统中,为了让多个进程能并发处理,在进程中进行上下文的切换。进程是操作系统中分配CPU、内存、外存资源的基本单位。
2、什么是线程?
线程是CPU处理的基本单位,但不独立分配资源。与进程相比,线程在程序运行时的上下文信息达到最小,线程也被成为轻量级的进程。线程由进程来负责管理和调度。
2.1一个线程的生命周期
2.2 线程的五种状态
- 新建状态(New) : 当线程对象对创建后,即进入了新建状态
- 就绪状态(Runnable) : 当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
- 运行状态(Running) : 当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
- **阻塞状态(Blocked) ?*处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1. 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2. 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二、线程的创建
1、通过继承Thread类的方法生成线程
1.1 实现顺序
1. 编写继承Thread类的Class
2. 在这个Class中重写run方法,将方法放入到run方法中
3. 实例化该Class对象
4. 用该实例化对象调用start()方法
1.2 代码如下:
public class MyThread extends Thread {
@Override
public void run() {
while (true){
System.out.println("MyThread类的闰方法正在执行");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
while (true){
System.out.println("mian()方法正在执行");
}
}
}
2、通过实现Runnable接口生成线程
2.1 实现顺序
1. 编写实现Runnable接口的类
2. 在该类中实现run方法,将要实现的方法放入run方法中
3. 实例化该类的对象
4. 把这个实例作为构造方法的参数生成Thread类的对象
5. 调用此Thread类的对象的start()方法
2.2 代码如下:
public class MyThread2 implements Runnable {
public MyThread2() {}
@Override
public void run() {
while (true){
System.out.println("MyThread2类的run方法在运行");
}
}
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
Thread thread = new Thread(myThread2);
thread.start();
while (true){
System.out.println("main方法在运行");
}
}
}
3、Thread和Runnable的区别
若一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口,就会实现非常容易的实现资源共享。
Runnable的优势 :
- 适合多个相同的程序代码的线程去处理同一个资源
- 避免单继承限制
- 提高程序健壮性,代码和数据独立,可以被多个线程共享
- 线程池中只能放如实现Runnable类线程
三、线程的常用方法
- Sleep(long m): 暂停当前线程多少毫秒
- setPriority(int i): 设置当前线程的优先级,范围1-10,值越大,优先级越高
- setPriority(): 返回当前线程的优先级
- currentThread(): 获得当前线程对象
- setName(String s): 设置线程名称
- getName(): 获得线程名称
- isAlive(): 测试该线程是否处于活动状态
- join(): 等待该线程终止,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
- Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
- wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。
四、线程同步与安全
线程安全一般出现在多线程中,而且基本只要出现资源共享的时候都会出现线程安全的问题,而为什么会出现这些问题关键在于多线程对共享资源进行操作时导致这个问题,如下例子:
package com.day505.testdemo.test;
public class SaleThread implements Runnable {
private int tickets = 10;
@Override
public void run() {
while (tickets>0){
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---卖出的票"+tickets--);
}
}
public static void main(String[] args) {
SaleThread saleThread = new SaleThread();
//创建并开启四个线程
new Thread(saleThread,"线程一").start();
new Thread(saleThread,"线程二").start();
new Thread(saleThread,"线程三").start();
new Thread(saleThread,"线程四").start();
}
}
运行结果
如上图所示,在进行资源共享时,四个线程对一个资源同时进行处理,在这个过程中就会出现了安全问题。为了解决这个问题可以使用同步代码块或者同步方法开解决这个问题。
同步代码块
当多个线程使用同一个共享资源时,可以将处理共享资源的代码放入到一个代码块中去,使用synchronized关键字来修饰,被称为同步代码块。
同步方法
在方法钱可以使用synchronized关键字来修饰,被修饰的方法为同步方法,能实现和同步代码块一样的功能。同步方法在某一时刻只能允许被一个线程访问,此时其他线程都会被堵塞,知道这个线程访问完毕,其他线程才有机会访问。
利用同步方法解决 代码如下:
package com.day505.testdemo.test;
public class SaleThread implements Runnable {
private int tickets = 10;
@Override
public void run() {
while (true){
saleTicket();
if (tickets<=0){
break;
}
}
}
private synchronized void saleTicket(){
if (tickets>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---卖出的票"+tickets--);
}
}
public static void main(String[] args) {
SaleThread saleThread = new SaleThread();
//创建并开启四个线程
new Thread(saleThread,"线程一").start();
new Thread(saleThread,"线程二").start();
new Thread(saleThread,"线程三").start();
new Thread(saleThread,"线程四").start();
}
}