多线程详解
进程与线程
进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。进程与线程的区别如图所示
Java中多线程实现的方式
Thread实现
/**
*run就是线程要执行的任务方法
*/
public class MyThread extends Thread{
@Override
public void run(){
//这里的代码,就是一条心得执行路径
//这个执行路径的触发方式,不是调用run方法,二是通过thread对象的start()来启动任务
for(int i=0;i<10;i++){
System.out.println("锄禾日当午"+i);
}
}
}
Runnable实现
public class MyRunnable implements Runnable{
@Override
public void run() {
//现成的任务
System.out.println(Thread.currentThread().getName());
/* for(int i=0;i<10;i++){
System.out.println("床寝明月光"+i);
}*/
}
二者区别
/**
* 实现Runnable接口 与继承 Thread相比有如下优势:
* 1.通过创建任务,然后给线程分配的方式来实现多线程, 更适合多个线程同时执行相同任务的情况
* 2.可以多实现,避免单继承带来的局限性
* 3.任务与线程本身是分离的,提高了程序的健壮性
* 4.后面学习的线程池技术,接受Runnable类型的任务,而不接受Thread类型的线程。
线程的状态变化
要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止。下面分别介绍一下这几种状态:
创建状态
在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。
就绪状态
新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。
运行状态
当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。
阻塞状态
一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
死亡状态
线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
在此提出一个问题,Java 程序每次运行至少启动几个线程?
回答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。
取得和设置线程的名称
class MyThread implements Runnable{ //实现Runnable接口
public void run(){
for(int i=0;i<3;i++){
System.Out.Println(Thread.currentThread().getName()+"运行, i="+i); //取得当前线程的名称
}
}
};
public class ThreadDemo{
public static void main(String args[]){
MyThread my=new MyThread(); //定义Runnable子类对象
new Thread(my).start; //系统自动设置线程名称
new Thread(my,"线程A").start(); //手工设置线程名称
}
};
线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<50;i++){
try{
Thread.sleep(500) ; // 线程休眠
}catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadSleepDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
}
};
.
中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
System.out.println("1、进入run()方法") ;
try{
Thread.sleep(10000) ; // 线程休眠10秒
System.out.println("2、已经完成了休眠") ;
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
return ; // 返回调用处
}
System.out.println("4、run()方法正常结束") ;
}
};
public class ThreadInterruptDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
try{
Thread.sleep(2000) ; // 线程休眠2秒
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
}
t.interrupt() ; // 中断线程执行
}
};
线程的分类
用户线程和守护线程
当用户线程不存在时,守护线程也随之消失
生产者与消费者问题
package Character04.Thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Demo4 {
/**
* 多线程通信问题, 生产者与消费者问题
* @param args
*/
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag) {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程同步的三种方法演示
方法一:同步代码块
方法二:同步方法
方法三:显式锁 l :参数为 true 就是公平锁,为false是非公品锁**
package Character04.Thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadSecurity {
public static void main(String[] args) {
//出现了线程不安全
Ticket runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
private Object o = new Object();
private Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
/*//解决线程不安全,***使用synchronaize(方法一:同步代码块)***
synchronized (o){
if(count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("卖票成功,余票为"+count+"张");
}
}*/
/* //线程安全方法二:**同步方法**
boolean flag = sale();
if(!flag){
break;
}*/
//**同步方法三:显式锁 l :参数为 true 就是公平锁,为false是非公品锁**
l.lock();
if(count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("卖票成功,余票为"+count+"张");
}
l.unlock();
}
}
public synchronized boolean sale(){
if(count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+":卖票成功,余票为"+count+"张");
return true;
}else{
return false;
}
}
}
}