一、进程与线程
进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。
多进程操作系统能同时运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的 CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好象是在“同时”运行一样。
线程是比进程更小的执行单位,线程是进程内部单一的一个顺序控制流。
所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行,形成多条执行线索。一个进程可能包含了多个同时执行的线程。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。
线程和进程的主要差别体现在以下两个方面:
①、同样作为基本的执行单元,线程是划分得比进程更小的执行单位。
②、每个进程都有一段专用的内存区域。与此相反,线程却共享内存单元(包括代码和数据),通过共享的内存单元来实现数据交换、实时通信与必要的同步操作。
多线程好处:解决了多部分同时运行的问题。还可以提高效率,非主应用(下载)。
多线程的弊端:线程太多回收效率的降低。
其实应用程序的执行都是CPU在做着快速的切换完成的。但是这个切换是随机的。
CPU执行到谁,谁就运行。调用线程的start方法:该方法有两个作用--启动线程--调用run方法。
二、创建多线程
通过对API查找,Java已经提供了对线程这类事物的描述,就是Thread类
创建线程的第一种方式:继承Thread类
步骤:
①定义类继承Thread
②复写Thread类中的run方法
复写的目的:将自定义的代码存储在run方法中,让线程运行
③调用线程的start方法
该方法有两个作用:a,启动线程;b,调用run方法
/*
* 需求:创建两个线程,和主线程交替运行
* 线程都有自己默认的名称,名称为:Thread-编号 (编号从0开始)
*
* static Thread currentThread():获取当前线程对象
* getName():获取线程名称
* 设置线程名称:setName或者构造函数
*/
package thread;
//创建线程Test继承Thread类
class Test extends Thread
{
private String name;
Test(String name)
{
this.name=name;
//super(name);
}
//复写run方法
public void run()
{
for(int x=0;x<70;x++)
{
System.out.println(this.getName()+"---run---"+x);
//System.out.println(Thread.currentThread().getName()+"---run---"+x);
}
}
}
public class ThreadTest1 extends Thread
{
public static void main(String[] args)
{
// TODO Auto-generated method stub
Test t1=new Test("One");//创建线程t1
Test t2=new Test("Two");
t1.start();//开启线程t1
t2.start();
for (int x=0;x<70;x++)
{
System.out.println("main..."+x);
}
}
}
运行结果:
发现运行结果每一次都不一样
因为,多个线程都获取CPU的执行权,CPU执行到谁,谁就运行;
明确一点:在某一时刻,只有一个程序运行(多核CPU除外)
这就是多线程的一个特性:随机性
为什么覆盖run方法?
Thread类用于多线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法;也就是说,Thread类中的run方法,用于存储线程要运行的代码
Start()和run()的特点:
Start()开启线程并执行该线程的run方法
run()仅仅是对象调用方法,而线程创建了,并没有执行
创建线程的第二种方式:实现Runnable接口
步骤:
1,自定义类实现Runnable接口
2,覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
3,通过Thread类建立线程对象
4,将Runnable接口的子类对象传递作为实际参数传递给Thread类的构造函数
为什么要将Runnable接口的子类对象作为实际参数传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定指定对象的run方法,就必须明确该run方法的对象。
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
售票程序例子:
<pre name="code" class="java">/*
* 需求:简单的多窗口同时售票程序
*/
package thread;
class Ticket implements Runnable
{
private int tick=100;
public void run()
{
while(true)
{
if(tick>0)
{
//打印线程名及票数
System.out.println(Thread.currentThread().getName()+".....sale: "+tick--);
}
}
}
}
public class SaleTicket
{
public static void main(String[] args)
{
// TODO Auto-generated method stub
//创建Runnable接口子类的实例对象
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();
}
}
实现方式和继承方式有什么区别?
实现方式的好处:避免了单继承的局限性
在定义线程时,建立使用实现方式
两种方式的区别:
继承Thread:线程代码存放在Thread子类run方法中
实现Runnable,线程代码存放在接口的子类run方法
三、多线程的几种状态
创建状态:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
四、多线程的安全问题
出现问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来,导致共享数据错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程都不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式。
——同步代码块
synchronized(对象)
{
需要被同步的代码;
}
对象如同锁,持有锁的线程可以在同步中执行
没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有锁
class TestThread implements Runnable{
private int tickets=20;
public void run()
{
while(true)
{
synchronized(this)
{
if(tickets>0)
{
try{
Thread.sleep(100);
}
catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);
}
}
}
}
}
同步的前提:
1,必须要有两个或两个以上的线程
2,必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源
——同步函数
访问控制符 synchronized 返回值类型 方法名称(参数)
{
…. ;
}
class TestThread implements Runnable{
private int tickets=20;
public void run(){
while(true){
<span style="white-space:pre"> </span>sale() ;
}
}
public synchronized void sale(){
if(tickets>0){
<span style="white-space:pre"> </span>try{
Thread.sleep(100);
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);
}
}
}
同步函数的锁是this
因:函数需要被对象调用,那么函数都一个所属对象引用,就是this
静态同步函数
静态方法中不可以定义this,所以锁不可能是this
静态同步函数的锁是是class对象
静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class,该对象的类型是class
故:静态同步方法是用的锁是该方法所在类的字节码文件对象:类名.class
多线程—单例设计模式—懒汉式
class Single
{
private static Single s=null;
private Single(){}
public static Single getInstance()
{
if(s==null)//双重判断的方式能解决效率问题
{
synchronized(Single.class)//使用的锁是该类的字节码文件对象:类名d.class
{
if(s==null)
s=new Single();
}
}
return s;
}
}
public class ThreadSingleLazy {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("多线程——单例设计模式——懒汉式");
}
五、死锁
一旦有多个进程,且它们都要争用对多个锁的独占访问,那么就有可能发生死锁。如果有一组进程或线程,其中每个都在等待一个只有其它进程或线程才可以执行的操作,那么就称它们被死锁了。
<pre name="code" class="java">/*
* 死锁程序
*/
package thread;
//定义一个类来实现Runnable,并复写run方法
class LockTest implements Runnable
{
private boolean flag;
LockTest(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(MyLock.locka)//a锁
{
System.out.println(Thread.currentThread().getName()+"------if_locka");
synchronized(MyLock.lockb)//b锁
{
System.out.println(Thread.currentThread().getName()+"------if_lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.lockb)//b锁
{
System.out.println(Thread.currentThread().getName()+"------else_lockb");
synchronized(MyLock.locka)//a锁
{
System.out.println(Thread.currentThread().getName()+"------else_locka");
}
}
}
}
}
}
//定义两个锁
class MyLock
{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLock
{
public static void main(String[] args)
{
//创建2个进程,并启动
new Thread(new LockTest(true)).start();
new Thread(new LockTest(false)).start();
}
}
要避免死锁,应该确保在获取多个锁时,在所有的线程中都以相同的顺序获取锁。
六、多线程间通讯
多线程间通信就是:多个线程在操作同一个资源,但是操作的动作不一样。
等待唤醒机制
------>wait();将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
------>notify();唤醒线程池中某一个等待线程。
------>notifyAll();唤醒的是线程池中的所有线程。
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义Object类中呢?唯一这些方法在操作同步中的线程时,都必须要标 识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
多线程间通讯范例:/*
* 线程间通讯:
* 其实就是多个线程操作同一对象,但是操作的动作不同
*/
package thread;
class Res
{
private String name;
private String sex;
private boolean flag=false;
public synchronized void set(String name,String sex)
{
if(flag)
try{this.wait();}catch(Exception e){}//如果有资源时,等待资源取出
this.name=name;
this.sex=sex;
flag=true;//表示有资源
this.notify();//唤醒等待
}
public synchronized void out()
{
if(!flag)
try{this.wait();}catch(Exception e){}//如果木有资源,等待存入资源
System.out.println(name+"------"+sex);//打印表示取出
flag=false;//已取出 资源
this.notify();
}
}
//存储线程
class Input implements Runnable
{
private Res r;
Input (Res r)
{
this.r=r;
}
public void run()
{
int x=0;
while(true)
{
if(x==0)
r.set("李雷Mike", "male");
else
r.set("韩梅梅Lily", "female");
x=(x+1)%2;//交替打印的控制语句
}
}
}
//取出线程
class Output implements Runnable
{
private Res r;
Output (Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
public class ThreadCommunication
{
public static void main(String[] args)
{
// TODO Auto-generated method stub
Res r=new Res();//建立资源,表示操作同一资源
new Thread(new Input(r)).start();//新建并开启存储线程
new Thread(new Output(r)).start();//新建并开启取出线程
}
}
升级JDK1.5中提供了多线程升级解决方案。
将同步synchronized 替换成了显示的Lock操作。
将Object中的wait()//notify()//notifyAll(),替换了Condition对象。
该对象可以Lock锁 进行获取。
停止线程:
Stop()过时了。
只有一种方法:run()方法结束。
① 开始多线程运行,运行代码通常都是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
②特殊情况:
当线程处于了冻结状态。
就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行处理。强制让线程恢复到运 行状态中来。这样就可以操作标记让线程结束。
lock范例:
/*
JDK1.5中提供了多线程升级解决方案
将同步Synchronized替换成实现Lock操作
将Object中的wait,notify,notifyAll替换成了condition对象
该对象可以Lock锁,进行获取
在该示例中,实现了本方只唤醒对方的操作
*/
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();//一个锁可对应多个condition
private Condition condition_con = lock.newCondition();
public void produce(String name)throws InterruptedException
{
lock.lock();//获取锁
try
{
if(flag)
condition_pro.await();//本方等待,需要抛出异常
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
condition_con.signal();//唤醒对方
}
finally
{
lock.unlock();//解锁,一定执行
}
}
public synchronized void consume()throws InterruptedException
{
lock.lock();
try
{
if(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+".....消费者....."+this.name);
flag = false;
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
try
{
r.produce("商品");
}
catch (Exception e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource r ;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
try
{
r.consume();
}
catch (Exception e)
{
}
}
}
}
class ProducerConsumerDemoPro
{
public static void main(String[] args)
{
Resource r = new Resource();//建立资源
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
Join:
当A线程执行到了B线程的。Jion()方法,A就会等待。等B线程都执行完,A才会执行。
Join可以用来临时加入线程执行。(比较少使用。Join跑出了异常,需要注意。)
优先级和yield:
优先级表示的是,运行频率。常用:1,5,10.对应的是:MAX_PRIORITY;MIN;NORM
Yield:表示的是暂停当前正在执行的线程对象,并执行其他线程。
public static void yield()
独立运算,相互之间不相干的时候单独封装下即可。