Java基础之多线程

来源:互联网 发布:刺客信条优化最好的 编辑:程序博客网 时间:2024/05/14 06:27

一、线程简介

要先了解线程就得知道进程。进程就是正在执行的程序,也就是程序执行的路径。线程就是进程中的独立控制单元,线程控制着进程的执行,一进程至少有一个线程,当有多个线程时,每个线程完成一个功能,并与其他线程并发执行,这种机制就叫多线程。当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 stubThreadDemo 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 stubint x=0;while(x<=10){System.out.println(x+++"  "+Thread.currentThread().getName());}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubThreadDemo1 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 blocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+"…………"+ticket--);}}}}public class ThreadTicket {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubTicket 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 blocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+"…………"+ticket--);}}}}}public class ThreadTicket {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubTicket 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 stubThread 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 blocke.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 blocke.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();}}

运行结果为:


0 0