一、线程简介
要先了解线程就得知道进程。进程就是正在执行的程序,也就是程序执行的路径。线程就是进程中的独立控制单元,线程控制着进程的执行,一进程至少有一个线程,当有多个线程时,每个线程完成一个功能,并与其他线程并发执行,这种机制就叫多线程。当JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。
多线程的意义:让程序同时运行,提高程序执行效率。
windows操作系统CPU的工作原理:系统可以分配给每个进程一段有限的CPU时间片,CPU在这段时间中执行某个进程,然后下一个时间片又跳到另一个进程中去执行。由于CPU这样的跳转很快,所以使得每个进程好像是同时执行一样。
二、实现线程的两种方式:
1、继承Thread类
通过继承Thread类,覆盖类中的run()方法,通过Thread类中的start()方法来执行线程。
创建步骤:1).定义一个类继承Thread类。2.)覆盖Thread类中的run方法。3).直接创建Thread的子类对象创建线程。4).调用start方法开启线程并调用线程的任务run方法执行。
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
示例:
package com.heima.thread;
public class ThreadDemo extends Thread{
/**
* @param args
*/
private int x=10;
public void run()
{
while(x>=0)
{
System.out.println(x--+" "+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadDemo a1=new ThreadDemo();
ThreadDemo a2=new ThreadDemo();
a1.start();
a2.start();
}
}
运行结果:
10 Thread-0
9 Thread-0
10 Thread-1
8 Thread-0
9 Thread-1
7 Thread-0
8 Thread-1
6 Thread-0
7 Thread-1
5 Thread-0
4 Thread-0
3 Thread-0
2 Thread-0
1 Thread-0
0 Thread-0
6 Thread-1
5 Thread-1
4 Thread-1
3 Thread-1
2 Thread-1
1 Thread-1
0 Thread-1
可见一个线程并非一次性执行完的。
2、实现Rannable接口
当我们已经继承了别的类但是又要实现多线程那怎么办,这时就可以用通过实现Runnable接口来实现。
创建步骤:
1).定义类实现Runnable接口。
2).覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3).通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传
递。为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4).调用线程对象的start方法开启线程。
实现Rannable接口的好处就是打破单继承的局限性。所以当创建多线程的时候应该用这种实现Rannable接口方式。
示例:
package com.heima.thread;
public class ThreadDemo1 implements Runnable{
public void run()
{
show();
}
private void show() {
// TODO Auto-generated method stub
int x=0;
while(x<=10)
{
System.out.println(x+++" "+Thread.currentThread().getName());
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadDemo1 a=new ThreadDemo1();
Thread t1=new Thread(a);
Thread t2=new Thread(a);
t1.start();
t2.start();
}
}
运行结果是:
如果start方法调用一个已经启动的线程,系统会抛出IllegalThreadStateException异常。
三、线程安全问题
先看一个例子:
package com.heima.thread;
/**
* 需求:简单的卖票程序,模拟4个线程同时卖100张票。多窗口同时卖票
* @author Administrator
*
*/
class Ticket implements Runnable{
private int ticket=100;
public void run()
{
while(true)
{
if(ticket>0)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"…………"+ticket--);
}
}
}
}
public class ThreadTicket {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果 :
通过结果可以看出 :买票居然卖出了负数,这是不可能的,这时多线程出现的安全问题,并且此时的CPU 利用率很高,那么为什么会出现在这种情况呢?
当多条语句在操作线程共享数据时,一个线程的语句只执行了一部分,这是另一个进程进来的,导致共享数据的错误。
解决办法是对操作共享数据的语句只让一个线程都执行完,而其他线程不能执行。这就需要
1)同步代码块,用关键字synchronized。
synchronized(对象)
{
需要被同步的代码
}
2)同步函数,格式:在函数上加上synchronized修饰符即可。它实用的锁是this。静态函数使用的锁是该类的字节码文件,即类.Class
上示例修改后的代码:
package com.heima.thread;
/**
* 需求:简单的卖票程序,模拟4个线程同时卖100张票。多窗口同时卖票
* @author Administrator
*
*/
class Ticket implements Runnable{
private int ticket=100;
Object obj=new Object();
public void run()
{
while(true)
{
synchronized (obj) {
if(ticket>0)
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"…………"+ticket--);
}
}
}
}
}
public class ThreadTicket {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果:
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,CPU利用率高无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
如何找出多线程中的安全问题:
1、明确那些代码是多线程代码
2、明确共享数据
3、明确多线程运行代码中哪些语句是操作共享数据的。
多线程之懒汉式单例模式:实例延迟加载,多线程访问时会有安全问题,加同步能解决,锁是该类的字节码文件。
示例:
<span style="white-space:pre"> </span>private static Single getInstance()
{
if(s==null)
{
synchronized (Single.class) {
if (s==null) {
s=new Single();
}
}
}
return s;
}
四、线程死锁问题
什么叫死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作
用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进锁。
示例:
package com.heima.thread;
/**
* 写一个死锁程序
* @author Administrator
*
*/
class Test implements Runnable{
private boolean flag;
Test(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
synchronized (MyLock.aObject)
{
System.out.println("我是if aObject");
synchronized (MyLock.bObject)
{
System.out.println("我是if bObject");
}
}
}
else {
synchronized (MyLock.bObject)
{
System.out.println("我是else bObject");
synchronized (MyLock.aObject)
{
System.out.println("我是else aObject");
}
}
}
}
}
class MyLock{
static Object aObject=new Object();
static Object bObject=new Object();
}
public class DeadLock {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread aThread=new Thread(new Test(true));
Thread bThread=new Thread(new Test(false));
aThread.start();
bThread.start();
}
}
运行结果:
五、线程间通信
多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
1). wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2). notify():唤醒线程池中的一个线程(任何一个都有可能)。
3). notifyAll():唤醒线程池中的所有线程。
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法, 监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定在object类中。
2、JDK1.5中提供了多线程升级解决方案。
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
下面是典型的生产者与消费者的问题
代码示例如下:
package com.heima.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.LookAndFeel;
/**
* 生产者与消费者问题
* @author Administrator
*
*/
class Resource2{
private String name;
private int count=1;
private boolean flag=false;
private Lock look=new ReentrantLock();
private Condition condition_p=look.newCondition();
private Condition condition_c=look.newCondition();
public void set(String name) throws Exception
{
look.lock();
try {
while(flag)
{
condition_p.await();
}
this.name=name+"------"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag=true;
condition_c.signal();
}
finally{
look.unlock();
}
}
public void out() throws Exception
{
look.lock();
try{
while(!flag)
{
condition_c.await();
}
System.out.println(Thread.currentThread().getName()+"...消费者............"+this.name);
flag=false;
condition_p.signal();
}
finally{
look.unlock();
}
}
}
class Productor2 implements Runnable{
private Resource2 re;
public Productor2(Resource2 re) {
super();
this.re = re;
}
public void run()
{
while(true)
{
try {
re.set("++烤鸭+++");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer2 implements Runnable{
private Resource2 re;
public Consumer2(Resource2 re) {
super();
this.re = re;
}
public void run()
{
while(true)
{
try {
re.out();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class ProConDemo2 {
public static void main(String[] args) {
Resource2 r=new Resource2();
Productor2 pro=new Productor2(r);
Consumer2 con=new Consumer2(r);
Thread t1=new Thread(pro);
Thread t3=new Thread(pro);
Thread t2=new Thread(con);
Thread t4=new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果为: