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);
}
}
}