Java线程
线程相关名词概念:程序、进程、线程;
程序:一行一行指令,存在电脑硬盘上的固定代码;
进程:将硬盘上的程序调入内存执行时所创建的,即正在运行的程序
线程:进程执行中的执行个体单元,当本进程中的所有线程单元全部执行完 毕时进程才算执行完成,然后程序运行结束;
*******************************************************************************
程序和进程关系:1:1(运行一个程序便会创建一个进程);1:n(同一程序可以在同一时间被多次打开执行,相互之间无关系);1:0(程序未调入内存,即程序还未被打开运行);
进程和线程关系:1:1(一个进程可以只有一个线程,单线程);1:n(通常一个进程对应多个线程,即多线程程序);但是一个进程一定会有一个线程,我们之前所写的所有的程序都是单线程(main函数);
*******************************************************************************
线程的创建及使用方式:
1、继承Thread类:
创建:创建一个类继承Thread类,然后重写里面的run()方法;
使用:通过创建此类的对象,然后调用此对象的start()方法;
例 class A extends Thread{
public void run(){
........
}
}
A a=new A(); a.start();
2、实现Runnable接口
创建:创建一个类实现Runnable接口,然后实现其run()方法;
使用:通过创建此类的对象,然后再创建一个Thread类对象,在其构造函数中将实现runnable的类对象当做参数传进去,包装成一个线程,然后调用Thread类对象的start()方法;
例class A implement Runnable{
Public void run(){
..........
}
} A a=new A(); Thread t=new Thread(a); t.start();
3、匿名内部类的方式:
创建即启动:直接在一个类的方法中创建一个Thread类的对象,只不过无对象名,然后重写其run()方法;
new Thread(){
Public void run(){
......
}
}.start();
先创建,后启动:先在类的方法中创建Thread对象(不匿名),重写其run()方法,然后在你想启动的地方调用它的start()方法;
Thread t=new Thread(){
Public void run(){
........
}
}; t.start();
三种方式的合适选择:
1. 当已经继承了别的类了时,已经不能再继承Thread类,只能选择实现Runable接口的方式;
2. 当不需要再继承其他类的时候,毫不犹豫选择继承Thread类,因为使用方便,不需要包装;
3. 线程逻辑很简单的,即需要线程做的东西很少时,可以选择用匿名内部类方式做;
*******************************************************************************
二、线程同步(通信)问题:
1、Synchronized关键字:
案例:
张三和李四:是两夫妻
事情:他们在中国银行开了一个户头,张三拿了银行卡,李四拿了存折,存了5000 块进去了
发生:某一天两个人没商量情况下,张拿着银行卡去了ATM机上准备去3000,正在 这个时候,李四她也拿着存着,去柜台取钱,准备取4000
由于银行卡与存折都能取钱,所以在这里存折与银行卡分别代表两个线程,当两个线程同时都去取钱时,就可能会出现异常情况;
解决:使用Synchronized关键字;
Synchronized:同步锁关键字(1.可以锁住方法 2.可以锁住指定的某个代码块);
作用:当某线程执行到被Synchronized修饰的方法或者同步代码块的时候,如果这个方法或代码块正在被其他线程操作,则此线程会阻塞,直到正在执行被 Synchronized修饰的方法或代码块的线程执行完Synchronized修饰的方法或代码块后才能进入执行,在即被Synchronized修饰的方法或者代码块在同一时刻只能被一个线程操作;
注意:Synchronized关键字在锁代码块时,需要传递一个参数,此参数可以使任何 对象类型,它代表被多个线程所共用的东西,即临界资源,加上Synchronized关键 字就是为了锁住它,让它在同一时刻只能被一个线程所访问,所以此参数必须是所 有线程在执行过程中所共用的东西;可参考生产消费者模型代码
*******************************************************************************
2、wait()与notify()方法的使用:
案例:生产者消费者模型;代码如下
/**
* 生产线程:生产产品(Phone),存入容器中
* @author minGe_000
*
*/
public class ProducteThread extends Thread{
private ArrayList<Phone> list;
public ProducteThread(ArrayList<Phone> list){
this.list = list;
}
@Override
public void run() {
int index = 1;
while(true){
synchronized (list) {
//检测容器当中是否还有产品,如果有产品,则不生产
if(list.size()>0){
try {
//阻塞代码
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产一个产品放到容器当中
Phone phone = new Phone(index+"号手机");
index++;
list.add(phone);
System.out.println("生产者生产了一个手机,型号是:"+phone.getName());
//赶紧通知消费来取产品
list.notify();
}
}
}
}
/**
* 消费线程:从容器当中,去除产品对象
* @author minGe_000
*
*/
public class CustomerThread extends Thread{
private ArrayList<Phone> list;
public CustomerThread(ArrayList<Phone> list){
this.list = list;
}
@Override
public void run() {
while(true){
synchronized (list) {
//检测容器当中是否还有产品,如果没有产品,则继续循环检测
if(list.size()==0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从容器当中取出一个产品
Phone phone = list.remove(0);
System.out.println("消费者消费了一个手机,型号是:"+phone.getName());
list.notify();
}
}
}
}
/**
* 产品类
* @author minGe_000
*
*/
public class Phone {
private String name;
public Phone(String name){
this.name = name;
}
public String getName(){
return name;
}
}
模型解析:生产者线程负责生产产品然后把产品装在容器中,消费者线程负责消费产品即从 容器中拿出产品,所以此模型相当于一个取一个拿,装产品的容器在这里属于临界资源, 即会被所有进程访问,在两个线程执行过程中,都会对此容器进行操作,所以我们把此 容器当做参数给Synchronized关键字,然后把所有对临界资源(在这里就是产品容器)进 行操作的代码放在Synchronized关键字里面,我们假设此容器只能装一个产品,当生产 者线程发现容器中有产品的时候,便会调用wait()(必须是同步锁的参数调用)方法进行 等待,直到被同步锁参数调用nodify()方法唤醒,在进入等待的同时会释放同步锁即允 许其他线程进入临界区,当消费者线程进入临界区,取走产品之后,便使用同步锁参数 调用nodify()方法唤醒等待的生产者线程,对于消费者线程发现容器中为空而进入等待 的情况与之相似;
注意:
1)wait和notify必须在同步锁之内(即被Synchronized关键字锁住的代码块或方法)使用
2)同步锁锁定对象(即同步锁参数)和调用wait、notify对象必须同一个
3)当对象wait挂起状态时候是会释放同步锁的
*******************************************************************************
三、线程池:
线程都有生命周期,一个线程的生命周期就是从创建到死亡的过程
但是创建线程的开销很大,若是任务是连续不断地,然后每次一来任务就创建出相应个数线程来执行任务,这样的开销是很大的,也很浪费资源,我们可以一下子创建出指定个数的线程存起来,定义一个容器来存取任务,当容器为空即无任务时便让线程wait()等待,容器不为空即有任务过来便nodify()唤醒线程相应个数进程执行任务,任务全部执行完后,由于已经没有任务,线程便会全部进入wait()状态,线程池就是实现这样的功能;
模型图示:
任务类:
public class MyTask implements Runnable{
public String name;
public MyTask(String name){
this.name = name;
}
public void work(){
System.out.println(name+"开始执行任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"任务执行完毕");
}
public void run() {
work();
}
}
线程类:
import java.util.LinkedList;
/**
* 执行线程
* @author minGe_000
*
*/
public class Worker extends Thread{
private String name;
//一定要先获取到任务队列
private LinkedList<MyTask> tasklist;
public Worker(String name,LinkedList<MyTask> tasklist) {
this.name = name;
this.tasklist = tasklist;
}
public void run() {
while(true){
MyTask task;
synchronized (tasklist) {
if(tasklist.size()==0){
try {
tasklist.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从任务队列中,取出一个任务,并执行这个人
task = tasklist.removeFirst();
}
System.out.println(name+"正在执行:"+task.name);
task.work();
System.out.println(name+"执行完毕:"+task.name);
}
}
}
线程池
import java.util.LinkedList;
/**
* 线程池类
* @author minGe_000
*
*/
public class MyThreadPool {
//定义两个容器:一个装执行线程,一个装任务
private Worker[] workers;
private LinkedList<MyTask> taskList = new LinkedList<MyTask>();
public MyThreadPool(int size){
workers = new Worker[size];
//往数组中初始化多个执行线程对象
for (int i = 0; i < workers.length; i++) {
//创建对象
Worker worker = new Worker(i+"号执行线程",taskList);
worker.start();
workers[i] = worker;
}
System.out.println("==================线程池已经初始化好了=================");
}
//定义一个函数
public void getTask(MyTask task){
synchronized (taskList) {
taskList.add(task);
taskList.notify();
}
}
}
主函数
public class Test {
public static void main(String[] args) {
MyThreadPool mtp = new MyThreadPool(5);
// ExecutorService es = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
MyTask task = new MyTask(i+"号任务");
// es.execute(task);
}
}
}
Java内置线程池类:
线程常用方法(注意配合API使用,因为有些方法已经过时):
currentThread() 返回当前运行的Thread对象。
wstop() 使调用它的线程立即停止执行,永远不要调用这个方法。
wsuspend() 使线程挂起,暂停运行。
wresume() 恢复挂起的线程,使其处于可运行状态(Runnable)。
wyield() 将CPU控制权主动移交到下一个可运行线程。
wsetPriority() 设置线程优先级。
wgetPriority() 返回线程优先级。
wsetName() 设置线程的名字。
wgetName() 返回该线程的名字。
wisAlive( ) 如果线程已被启动并且未被终止,那么isAlive( )返回true。如果返回false,
HYDY编写