------
Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、进程和线程的概念
一台电脑上,QQ、音乐播放器、浏览器、记事本、游戏等程序在同时进行,这台电脑中每一个正在运行的程序都可以被称之为一个进程。
以音乐播放器这一进程为例,音乐播放器程序在播放音乐的时候,我点击查看歌词面板,程序会跳转到歌词面板。播放音乐和查看歌词面板这两个动作就可以称之为两个线程。
用学术性的语言来讲,进程就是一个正在运行着的程序,线程是进程中的一个独立的控制单元。
二、创建线程的两种方式
package com.itheima;
public class Demo{
public static void main(String[] args) throws Exception{
//通过继承创建线程,并启动线程
xc1 x= new xc1();
x.start();
//通过实现Runnable接口创建线程,并启动线程
xc2 y =new xc2();
Thread t = new Thread(y);
t.start();
}
}
//通过继承创建线程
class xc1 extends Thread{
public void run(){
System.out.println("xc1run");
}
}
//通过实现Runnable接口创建线程
class xc2 implements Runnable{
public void run(){
System.out.println("xc2run");
}
}
三、线程的四种状态
1.运行状态:具有执行资格并具有执行权
2.临时状态:具有执行资格,不具有执行权
3.冻结状态:不具有执行资格,不具有执行权
4.消亡状态:不具有执行资格,不具有执行权
四、线程安全问题
当多条语句在操作一个线程中的共享数据时,该线程对多条语句只执行了一部分,还没执行完,另一线程就开始执行从而使共享数据发生了改变,最终结果是共享数据出现异常。这就是线程出现了安全问题。我们可以通过Sychronized(){}同步代码块或同步函数或Lock对象来解决线程安全问题。
下面通过火车站窗口的买票小程序来演示线程安全问题及其处理办法。
/*
* 需求:模拟一个买票小程序,要求4个窗口同时售票,并且不能出售同号票。
* 思路:
* 1. 4个窗口即四个线程,票是4个线程的共享资源
* 2. 声明一个票类
* 3. 创建四个线程,让4个线程执行同样的买票代码,注意涉及到票的代码要加同步。
*/
package com.itheima;
public class Demo{
public static void main(String[] args){
//创建票的实例化对象,并将其传给四个线程
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
Thread t4 = new Thread(ticket);
//启动四个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//创建一个Ticket类,用来描述共享资源对象。该类需要实现Runnable接口方便被线程调用。
class Ticket implements Runnable{
private int count=100;//定义票的总数量为100
Object obj =new Object();//随便创建一个对象作为同步代码块的锁
Ticket(){} //票的构造方法,用于创建票的实例化对象
public void run(){ //复写Runnable的run方法
synchronized(obj){ //多条语句操作共享资源对象,需要加同步
if(count>0)
System.out.println(Thread.currentThread().getName()+"......"+count--);
}
}
}
开发中,解决线程安全问题的步骤:
1.明确那些代码是多线程运行代码 2.明确共享数据 3.明确多线程代码中哪些语句是操作共享数据的
五、死锁
所谓死锁,是指多线程程序在运行的过程中,当1线程的A锁中嵌套着B锁,并且2线程的B锁中嵌套着A锁时,由于两个线程都不能释放锁从而导致两个线程都不能正常执行,最终导致程序卡住。下面用一段代码演示什么是死锁。
/*
* 需求:模拟一个死锁程序
*/
package com.itheima;
public class Demo{
public static void main(String[] args){
//创建一个资源类的实例化对象,并将其传给两个线程
Res res = new Res();
Thread t1 = new Thread(res);
Thread t2 = new Thread(res);
//启动线程
t1.start();
t2.start();
}
}
//声明一个lockDemo类用于创建锁对象
class lockDemo{
lockDemo(){}
}
//声明一个资源类,实现Runnable接口以供线程调用
class Res implements Runnable{
int x;
lockDemo loc1 ;
lockDemo loc2;
boolean flag = false;
public void run(){
if(flag){
while(true){
synchronized(loc1){ //loc1锁中嵌套loc2锁
System.out.println("if_loc1");
synchronized(loc2){
System.out.println("if_loc2");
}
}
}
}
else{
while(true){
synchronized(loc2){ //loc2中嵌套loc1锁
System.out.println("else_loc2");
synchronized(loc1){
System.out.println("else_loc1");
}
}
}
}
}
}
六、线程间通讯
当两个线程同时操作同一个资源,1线程存储资源,2线程消耗资源,并且要求两个线程交替逐次进行,这时候就需要用到线程通讯。线程通讯的主要方法有:sleep() wait() notify() notifyAll() 。。。JDK1.5.0以后出现升级解决方案,开始使用await() signal() signalALL() 等方法。。。下面通过生产者和消费者的模型演示线程间通讯。
/*
* 需求:生产者和消费者交替逐次进行的线程通讯演示(使用lock方法)
* 思路:
* 1.声明3个类,生产类、消费类和资源类。
* 2.生产类供生产线程调用,消费类供消费线程调用,资源类被共同调用
* 3.利用线程通讯方法控制线程的交替执行
*/
package eee;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo{
public static void main(String[] args){
Resource res = new Resource("商品",1);
new Thread(new Pro(res)).start();
new Thread(new Con(res)).start();
}
}
//声明一个资源类
class Resource{
private String name;
private int num;
Resource(String name,int num){
this.name=name;
this.num=num;
}
boolean flag = false;
Lock lock = new ReentrantLock();//声明一个锁对象
Condition condition_pro = lock.newCondition();//声明一个condition_pro对象,用于控制生产线程
Condition condition_con = lock.newCondition();//声明一个condition_con对象,用于控制消费线程
//资源内部封装一个生产资源的方法
public void product(){
lock.lock();
try{
while(true){//循环生产
while(flag){
try{condition_pro.await();}catch(InterruptedException ie){}
}
System.out.println(Thread.currentThread().getName()+"...生产..."+this.name+this.num);
num++;
flag=true;
condition_con.signal();//唤醒消费线程
}
}
finally{
lock.unlock();
}
}
//资源内部封装一个消费资源的方法
public void consumer(){
lock.lock();
try{
while(true){//循环消费
while(!flag){
try{condition_con.await();}catch(InterruptedException ie){}
}
System.out.println(Thread.currentThread().getName()+"...消费..."+this.name+this.num);
flag=false;
condition_pro.signal();//唤醒生产线程
}
}
finally{
lock.unlock();
}
}
}
//声明一个生产者线程,需要实现Runnable接口
class Pro implements Runnable{
Resource res;
Pro(Resource res){
this.res=res;
}
public void run(){
res.product();
}
}
//声明一个消费者线程,需要实现Runnable接口
class Con implements Runnable{
Resource res;
Con(Resource res){
this.res=res;
}
public void run(){
res.consumer();
}
}
七、停止线程
停止线程需要使用stop方法或者让run方法结束,由于stop方法已经过时所以只能通过让run方法结束的方式。具体做法是给循环定义一个flag标记,当catch捕获到wait抛出的异常进行处理时改变flag的值。代码演示如下。
package com.itheima;
public class Demo3{
public static void main(String[] args){
stopThread st = new stopThread();
new Thread(new stopThread()).start();
new Thread(new stopThread()).start();
for(int x=0;x<70;x++){
System.out.println(Thread.currentThread().getName()+"....run");
}
}
}
//声明一个类,实现Runnable接口
class stopThread implements Runnable{
boolean flag=true;
public synchronized void run(){
while(flag){
System.out.println(Thread.currentThread().getName()+"....run");
try{
wait();
}
catch(InterruptedException ie){
flag= false;
}
}
}
}
八、守护线程、加入线程
守护线程:t.setDae(true) 主线程结束后,守护线程自动结束
加入线程:t.join() 线程加入后,主线程进入冻结状态,等加入的线程执行完毕,主线程才会唤醒