多线程

1.概述

进程:一个程序的执行。
  线程:程序中单个顺序的流控制。
  一个进程中至少要有一个线程,可以含有多个线程。一个进程中的多个线程分享CPU(并发的或以时间片的方式),共享内存(如多个线程访问同一对象)。Java支持多线程,如:Object中wait(),notify()。
  多线程解决了多部分代码同时运行的问题。但线程太多,会导致效率的降低。
  JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
  1)执行main函数的线程,该线程的任务代码都定义在main函数中。
   2)负责垃圾回收的线程。System类的gc方法告诉垃圾回收器调用finalize方法,但不一定立即执行。
  线程体是由run()方法来实现的。线程启动后,系统就自动调用run()方法。通常,run()方法执行一个时间较长的操作,如一个循环,显示一系列图片或下载一个文件等。
  对线程的基本控制:
  1)线程的启动:start()
  2)线程的结束:设定一个标记变量,以结束相应的循环及方法。
  3)暂时阻止线程的执行:try{ Thread.sleep( 1000 );} catch( InterruptedException e ){ }
  4)设定线程的优先级:setPriority(int priority)方法:MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY

线程的状态:

2.创建线程的两种方法

1.继承Thread类
  a.定义一个类继承Thread类。
  b.覆盖Thread类中的run方法。
  c.直接创建Thread的子类对象创建线程。
  d.调用start方法开启线程并调用线程的任务run方法执行。

class Demo extends Thread{
	private String name;
    Demo(String name){
    	this.name = name;
    }
    public void show(){
    	for( int x = 0; x < 10; x++){
    		//通过Thread的getName方法获取线程的名称。
    		System.out.println(name + "...x = " + x + "...ThreadName = " + Thread.currentThread().getName());
    	}
    }
}
public class ThreadDemo {
    public static void main(String[] args){
    	Demo d1 = new Demo("Amy");
    	Demo d2 = new Demo("Lucy");
    	//开启线程,调用run方法
    	d1.start();
    	d2.start();
    	for (int x = 0; x < 20; x++){
    		System.out.println("x = " + x + "...over..." + Thread.currentThread().getName());
    	}
    }
}

2.实现Runnable接口
  a.定义类实现Runnable接口;
  b.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
  c.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
  d.调用线程对象的start方法开启线程。
实现Runnable接口的好处:
 1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
 2.避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

class Demo2 implements Runnable{
	//覆盖接口中的run方法,将线程的任务代码封装到run方法中。
	public void run(){
		show();
	}
	public void show(){
		for (int x = 0; x < 20 ; x++){
			System.out.println(Thread.currentThread().getName()+"..."+x);
		}
	}
}
public class ThreadDemo2 {
	public static void main(String[] args){
		Demo2 d = new Demo2();
		//通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
	}
}

3.线程同步

线程安全问题产生的原因:同时运行的线程需要共享数据。
  线程安全问题的解决方案:Java引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个monitor(监视器),它上面一个称为“互斥锁(lock, mutex)”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。关键字synchronized 用来与对象的互斥锁联系。
  同步代码块:
  synchronized(对象){
    需要被同步的代码;
  }
  同步函数:
  synchronized 放在方法声明中。
  
  同步的好处:解决了线程的安全问题。
  同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
  同步的前提:必须有多个线程并使用同一个锁。

/*
售票问题
*/
class Ticket implements Runnable{
	private int num = 100;
	boolean flag = true;
	public void run(){
		if (flag){
			while(true){
			    //同步代码块,锁是任意的对象,synchronized(this)表示整个方法为同步方法。
				synchronized(this){
					if (num > 0){
						try{
							Thread.sleep(10);
						}catch(InterruptedException e){
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()+"...obj..." + num--);                                               
					}			
			}
		}
	}else
		while(true)
			show();
	}
	//同步函数,锁是固定的this
	public synchronized void show(){
		if (num > 0){
			try{
				Thread.sleep(10);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"...function..." + num--);
		}
	}
}
public class TicketDemo {
	public static void main(String[] args){
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{//下面这条语句一定要执行。因为可能线程t1尚未真正启动,flag已经设置为false,
			//那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况
			//执行,实验就达不到目的了。
			Thread.sleep(10);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		t.flag = false;
		t2.start();
	}
}

静态的同步代码块使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

多线程下的单例模式
  饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。

/*
 * 加同步的单例模式——懒汉式
 */
public class Single {
	private static Single s = null;
	private Single(){}
	//若使用同步函数,则效率较低,因为每次都需要判断
	public static Single getInstance(){
		if(s == null){
			synchronized(Single.class){ //synchronized(this.getClass)
				if (s == null)
					s = new Single();
			}
		}
		return s;
	}
}

4.死锁

死锁常见情景之一:同步的嵌套。

class Test implements Runnable{
	private boolean flag;
	Test(boolean flag){
		this.flag = flag;
	}
	public void run(){
		if(flag){
			while(true)
				synchronized(MyLock.locka){
					System.out.println(Thread.currentThread().getName()+"...if locka...");
					synchronized(MyLock.lockb){
						System.out.println(Thread.currentThread().getName()+"...if lockb...");
					}
				}
		}else{
			while(true)
				synchronized(MyLock.lockb){
					System.out.println(Thread.currentThread().getName()+"...else lockb...");
					synchronized(MyLock.locka){
						System.out.println(Thread.currentThread().getName()+"...else locka...");
					}
				}
		}
	}
}
class MyLock{
	public static final Object locka = new Object();
	public static final Object lockb = new Object();
}
class DeadLockDemo {
	public void main(String[] args){
		Test a = new Test(true);
		Test b = new Test(false);
		Thread t1 = new Thread(a);
		Thread t2 = new Thread(b);
		t1.start();
		t2.start();
	}
}

5.线程间通信

wait():让线程处于阻塞状态,被wait的线程会被存储到线程池中。
notify():唤醒线程池中的一个线程(任何一个都有可能)。
notifyAll():唤醒线程池中的所有线程。

/*
 * 文件架实例,一个存一个取。
 */
class CubbyHole{
	private int index = 0;
	private int[] data = new int[3];
	//向data中输入数,当data满时阻塞,否则向里面存入一个数,并唤醒另一个线程
	public synchronized void put(int value){
		while(index == data.length){
			try{
				//阻塞线程
				this.wait();
			}catch(InterruptedException e){}
		}
		data[index] = value;
		index ++;
		//唤醒线程
		this.notify();
	}
	//从data中取数,当data为空时阻塞,否则从中取出一个数,并唤醒另一个线程
	public synchronized int get(){
		while(index <= 0){
			try{
				//阻塞线程
				this.wait();
			}catch(InterruptedException e){}
		}
		index --;
		int val = data[index];
		//唤醒线程
		this.notify();
		return val;
	}
}

JDK1.5中增加了更多的类,以便更灵活地使用锁机制:java.util.concurrent.locks包,Lock接口、ReentrantLock类,ReadWriteLock接口、ReentrantReadWriteLock类。
  JDK1.5中将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
  Lock接口:替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
  lock():获取锁。
  unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
  Condition接口:替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
  await() —> wait()
  signal() —> notify()
  signalAll() —> notifyAll()

/*
 *多生产者-多消费者问题 
 */
import java.util.concurrent.locks.*;
class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	//创建一个锁对象
	Lock lock = new ReentrantLock();
	//通过已有的锁获取该锁上的监视器对象
	Condition con = lock.newCondition();
	public void set(String name){
		lock.lock();
		try{
			while(flag) //重复判断生产者是否生产
				try{
					con.await();//生产者等待
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			this.name = name + count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
			flag = true;
			con.signalAll();//唤醒消费者
		}finally{
			lock.unlock();//解锁
		}
	}
	public void out(){
		lock.lock();
		try{
			while(!flag) //重复判断是否可以消费
				try{
					con.await();//消费者等待
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
			flag = false;
			con.signalAll();//唤醒生产者
		}finally{
			lock.unlock();//解锁
		}
	}
}
//生产者
class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r = r;
	}
	//复写run()
	public void run(){
		while(true){
			r.set("Item");
		}
	}
}
//消费者
class Consumer implements Runnable{
	private Resource r;
	Consumer(Resource r){
		this.r = r;
	}
	//复写run()
	public void run(){
		while(true){
			r.out();
		}
	}
}

public class ResourceDemo {
	public static void main(String[] args){
		Resource r  = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t0 = new Thread(pro); //生产者线程t0
		Thread t1 = new Thread(pro); //生产者线程t1
		Thread t2 = new Thread(con); //消费者线程t2
		Thread t3 = new Thread(con); //消费者线程t3
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}

停止线程:
  1)运行状态的线程:结束循环
  2)阻塞状态的线程:使用interrupt()方法,让线程具备CPU的执行资格。强制动作会发生InterruptedException。

class StopThread implements Runnable{
	private boolean flag = true;
	public void run(){
		while(flag){
			try{
				wait();
			}catch(InterruptedException e){
				System.out.println(Thread.currentThread().getName()+"..."+e);
				flag = false;
			}
			System.out.println(Thread.currentThread().getName()+"......");
		}
	}
	public void setFlag(){
		flag = false;
	}
}
public class StopThreadDemo {
	public static void main(String[] args){
		StopThread st = new StopThread();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		t1.start();
		t2.start();
		int num = 1;
		for(;;){
			if(++num == 50){
				//使用interrupt()方法来结束阻塞状态的线程
				t1.interrupt();
				t2.interrupt();
				break;
			}
			System.out.println("main..."+num);
		}
		System.out.println("over");
	}
}

6.线程类的其他方法

线程有两类:一类是普通线程(非Daemon线程)。在Java程序中,若还有非Daemon线程,则整个程序就不会结束。另一类是Daemon线程(守护线程,后台线程)。如果普通线程结束了,则后台线程自动终止。如垃圾回收线程就是后台线程,使用setDaemon(true)方法。
  join():等待该线程终止
  toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
  yield():暂停当前正在执行的线程对象,并执行其他线程。

class Demo1 implements Runnable{
	public void run(){
		for (int x = 0; x < 50; x++){
			System.out.println(Thread.currentThread().getName()+"..."+x);
			Thread.yield(); //释放执行权
		}
	}
}
public class JoinDemo {
	public static void main(String[] args){
		Demo1 d = new Demo1();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		try{
			t1.join();//t1线程要申请加入进来,运行。然后,主线程等待t1执行完。
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		t2.start();
		t2.setPriority(Thread.MAX_PRIORITY);
		for(int x = 0; x < 50; x++){
			System.out.println(Thread.currentThread().toString()+"..."+x);
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值