java 多线程简述
来源:互联网 发布:华丽上班族电影知乎 编辑:程序博客网 时间:2024/06/04 17:56
多线程的基本实现
进程指运行中的程序,每个进程都会分配一个内存空间,一个进程中存在多个线程,启动一个JAVA虚拟机,就是打开个一个进程,一个进程有多个线程,当多个线程同时进行,就叫并发。
J ava创建线程的两种方式为: 继承Thread类 和实现Runnable接口
Thread类
1、通过覆盖run方法实现线程要执行的程序代码
2、Start()开始执行多线程
package com.bin.duoxiancheng;public class d1 extends Thread{ public void run(){ for(int i=0 ; i<50; i++){ System.out.println(i); System.out.println(currentThread().getName()); try { sleep(100); } catch (InterruptedException e) { // TODO Auto-generatedcatch block e.printStackTrace(); } } } public static void main(String[] args){ new d1().start(); new d1().start(); }}
多个线程共享一个实例的时候,代码代码如下:
package com.bin.duoxiancheng;public class d1 extends Thread{ int i=0; public void run(){ for(i=0 ; i<50; i++){ System.out.println(i); System.out.println(currentThread().getName()); try { sleep(100); } catch (InterruptedException e) { // TODO Auto-generatedcatch block e.printStackTrace(); } } } public static void main(String[] args){ new d1().start(); new d1().start(); }}
结果如下所示:
0
Thread-1
0
Thread-0
1
Thread-1
1
实际2个线程在操纵不同的变量a,在执行run方法时候,线程把a都当做自己的变量在执行。
Runnable接口实现多线程
当一个继承自Thread时,就不能再继承其他类,使用Runnable接口解决了此问题,在新建一个Thread类中,在构造方法中初始化
Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target,String name)
分配新的 Thread 对象。
package com.bin.duoxiancheng;public class D2 implements Runnable{ int i=0; public void run(){ for(i=0 ; i<50; i++){ System.out.println(i); System.out.println(Thread.currentThread().getName()); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generatedcatch block e.printStackTrace(); } } } public static void main(String[] args){ D2 d=new D2(); Thread t=new Thread(d); t.start(); }}
线程中状态之间的转变
New状态,使用new创建的线程对象处于新建状态,仅仅在堆栈中分配了内存。
Runnable就绪状态。当线程调用start方法后,线程进入就绪状态。虚拟机会为他创建方法调用栈和程序计数器,处于这个状态的线程位于可运行池中,等待获得CPU使用权。
Running运行状态
这个状态的线程正在占用CPU,如果一个计算机只有一个CPU,那么这个时刻会有一个线程处于这个状态,如多个CPU,可有多个线程占用不同的CPU,只有处于就绪状态的线程才有机会变为运行状态。
阻塞状态(Blocked)
由于某些原因放弃CPU暂时停止运行,此时虚拟机不会给线程分配CPU,直到重新进入就绪状态,
阻塞状态可分为以下3种。
1、 当处于运行状态中的某个对象执行了wait()方法,虚拟机就把这个线程放到这个对象等待池中。wait()是用来锁定一个对象的,在调用这个notify()方法前,他后面的代码永远不会被执行。这种锁定不能够自动解锁, 你必须在另一个线程中调用这个对象的notify()方法。
2、 当线程处于运行状态,试图获得某个对象的同步锁,若同步锁被其他线程占用,虚拟机会把这个线程放到这个对象的锁池中。
3、 当线程执行了sleep方法,或者调用join方法,或者发出了I/O请求时候,就会进入这个状态.
当线程执行System.out.println或者System.in.read()方法时,就会发出I/O请求,线程放弃cpu进入阻塞状态,直到I/O处理完成,
死亡状态(dead)
当线程退出run()方法时,就进入死亡状态,有可能正常 执行完run方法退出,也有可能异常退出,
Thread类中的isAlive()方法判断一个线程是否活着,当处于死亡状态或者新建状态时,该方法返回false,否则返回TRUE。
线程的调度
虚拟机按特定的机制为CPU分配使用权,主要有两种调度模式,分时调度模型和抢占式调度模型。分时调度模型就是线程轮流让CPU获得使用权,平均分配线程。抢占式调度为让线程中优先度高的占用CPU,若优先级相同,那么就随机选择一个。处于运行状态的线程会由以下原因放弃CPU
1、虚拟机转到就绪状态,其他线程获得机会。
2、线程进入阻塞状态;
3、线程运行结束
看以下程序:
package com.bin.duoxiancheng;public class D3 extends Thread { public static int count=0; public static StringBuffer log=new StringBuffer(); public void run(){ for(int a=0;a<20;a++){ log.append(currentThread().getName()+":"+a+" "); if(++count %10 ==0)log.append("\n"); } } public static void main(String[] args) throws InterruptedException { // TODO Auto-generatedmethod stub D3 d1= new D3(); D3 d2 =new D3(); d1.setName("m1"); d2.setName("d2"); d1.start(); d2.start(); while(d1.isAlive()||d2.isAlive()) Thread.sleep(500); System.out.print(log); }}
结果为:
m1:0 m1:1 m1:2 m1:3 m1:4 m1:5m1:6 m1:7 m1:8 m1:9
m1:10 m1:11 m1:12 m1:13 m1:14m1:15 m1:16 m1:17 m1:18 m1:19
m2:0 m2:1 m2:2 m2:3 m2:4 m2:5m2:6 m2:7 m2:8 m2:9
m2:10 m2:11 m2:12 m2:13 m2:14 m2:15 m2:16m2:17 m2:18 m2:19
程序中m1先获得主动权,直到结束才交予m2,如果想让一个线程交给另外一个线程运行可以采取以下方法:
1、 调整优先级;调用Thread.setPriority(Thread.MAX_PRIORITY),参数取值范围为1-10,值越高优先度越高。
2、 调用Thread.sleep()方法;参数以毫秒为单位,当睡眠结束时候就会转为就绪状态,当另一个线程处于运行状态中,此线程在运行池 中处于等待状态
3、 调用Thread.yield()方法,如有有相同优先级别的线程处于就绪状态,那么yield把当前运行的线程放到可运行池中并使另一个线程运行;若没有相同级别的,则不运行,执行yield后,当前线程进入就绪状态,跟SLEEP不同,sleep为进入阻塞状态。
4、 调用JOIN方法;使用此方法后,当前程序进入阻塞状态。直至另一个程序结束才会恢复 运行。
package com.bin.duoxiancheng;public class D5 extends Thread{ public void run(){ for(int a=0;a<50;a++){ System.out.println(getName()+":"+a); } } public static void main(String[] args) throws InterruptedException { D5 d=new D5(); d.setName("m1"); d.start(); System.out.println("main: joinmachine"); d.join(); System.out.println("main:end"); }}
System.out.println("main:end");这段程序要等到d这个线程结束后方执行。
也可以写作d.join(10)超过10毫秒恢复运行。
Timer类定时器的使用
参数使用如下:
(1)Timer.schedule(TimerTask task,Datetime)安排在制定的时间执行指定的任务。
(2)Timer.schedule(TimerTask task,Date firstTime ,long period)安排指定的任务在指定的时间开始进行重复的固定延迟执行.
(3)Timer.schedule(TimerTask task,long delay)安排在指定延迟后执行指定的任务.
(4)Timer.schedule(TimerTask task,long delay,long period)安排指定的任务从指定的延迟后开始进行重复的固定延迟执行. 第一个参数是要操作的方法,第二个参数是要设定延迟的时间,第三个参数是周期的设定,每隔多长时间执行该操作。
(5)Timer.scheduleAtFixedRate(TimerTask task,Date firstTime,long period)安排指定的任务在指定的时间开始进行重复的固定
速率执行.
(6)Timer.scheduleAtFixedRate(TimerTask task,long delay,long period)安排指定的任务在指定的延迟后开始进行重复的固定速率执行.
public class EggTimer { private final Timer timer = new Timer(); private final int minutes; public EggTimer(int minutes) { this.minutes = minutes; } public void start() { timer.schedule(new TimerTask() { public void run() { playSound(); // timer.cancel(); } private void playSound() { System.out.println("Your egg is ready!"); // Start a new thread to play a sound... } }, 0,minutes * 60 * 1000);//几分钟执行一次 } public static void main(String[] args) { EggTimer eggTimer = new EggTimer(2); eggTimer.start(); }}
同步代码块
为了保证线程能正常的执行原子操作,java引入了同步机制,在代码原子操作的程序代码前加上synchronized标记 ,这样,任何时刻只允许一个线程拥有这把锁,
这样当加了同步标记后,其他待进入线程放到Stack对象锁池中,线程进入阻塞状态。加锁方式如下:
Public synchronized String pop(){…}
也可以写作
Public String pop(){
Synchronized(this){…}
}
下面看一个消费者,创造者的例子
public class SyncTest { public static void main(String[] args) { Stack stack =new Stack("stack"); Producer producer1=new Producer(stack,"producer1"); Consumer consumer1=new Consumer(stack,"consumer"); }}class Producer extends Thread{ private Stack theStack; public Producer(Stacks,String name){ super(name); theStack = s; start(); } public void run(){ String goods; for(int i=0;i<20;i++){ goods="goods"+(theStack.getPoint()+1); theStack.push(goods); System.out.println(getName()+": push "+goods+" to "+theStack.getName()); yield(); } }}class Consumer extends Thread{ private Stack theStack; public Consumer(Stacks,String name) { super(name); theStack=s; start(); } public void run(){ String goods; for(int i =0;i<20;i++){ goods=theStack.pop(); System.out.println(getName()+":pop "+goods+" from "+theStack.getName()); yield(); } }} class Stack{ private String name; private String[] buffer=new String[100]; int point = -1; public Stack(String name){this.name=name;} public String getName(){return name;} public int getPoint(){return point;} public synchronized String pop(){ String goods=buffer[point]; buffer[point]=null; Thread.yield(); point --; return goods; } public synchronized void push(String goods){ point ++; Thread.yield(); buffer[point] =goods; } }
同步和并发问题
同步锁解决资源共享资源竞争的有效手段,当一个线程已经操作共享资源,其他线程只有等待,只有当已经在操作共享资源的线程执行完毕后才能同步代码块,其他线程才能有机会共享。
以打水为例子,10个去打水,每人要打10桶,使用同步方式为,一个人打完10桶后,其他人才有机会打水,,当一个人打水,其他人必须等待,显然不合理。
public class Person extends Thread{private Well well;public Person (Well well){ this.well=well; start();}public void run(){ synchronized(well){ for(int i=0;i<10;i++){ well.withdraw(); yield(); } }} /** * @param args */ public static void main(String[] args) { Well well=new Well(); Person persons[]=new Person[10]; for(int i=0;i<10;i++){ persons[i]=new Person(well); } } }class Well{ private int water=100; public void withdraw(){ water --; System.out.println(Thread.currentThread().getName()+": water left:"+water); }}
则我们把同步锁加在withdraw(),则可以解决问题,例:
public class Person extends Thread{private Well well;public Person (Well well){ this.well=well; start();}public void run(){ for(int i=0;i<10;i++){ well.withdraw(); yield(); }} public static void main(String[] args) { Well well=new Well(); Person persons[]=new Person[10]; for(int i=0;i<10;i++){ persons[i]=new Person(well); } }}class Well{ private int water=100; public synchronized void withdraw(){ water --; System.out.println(Thread.currentThread().getName()+": water left:"+water); }}
线程安全类
一个线程安全类必须满足以下条件:
1、 这个类可以同时被多个线程安全访问。
2、 每个线程都能正常执行原子操作,得到正确结果。
3、 原子操作万仇,对象处于逻辑是合理状态。
如上我们使用的Stack类就是可变类,point和buffer都可以发生变化,会造成共享资源的竞争。
线程之间的通信
不同线程之间执行不同的任务,如果任务有某种联系,必须实现通信功能,协调完成工作。
Java.lang.object类提供了用于线程通信的方法:
1、 wait(),该方式的线程释放对象锁,虚拟机把该对象放到等待池中,等待其他线程唤醒,并且释放所持有的对象的lock。
2、 notify(),该方法唤醒在等待池中的线程,虚拟机从等待池中随机选择一个线程.
3、 notifyAll()方法用于把对象池中所有的线程都转到对象锁池中。
例t1线程和t2线程可用wait和notify进行通信。
以编程的方式控制线程
实际编程中,一般是在受控的线程中定义一个标志量,其他线程通过改变标准变量的值,来控制线程的暂停、恢复运行已经自然终止,jdk1.2开始一些废弃了一些停止线程的方法
suspend() 使线程暂停,调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend().
resume() 暂停的线程恢复运行;
stop() 终止线程,它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在;
废弃的原因大概由于以上几个方法容易导致死锁等。
ThreadLocal类
java.lang.ThreaLocal一般用来存放线程的局部变量,每个线程一般都有单独的局部变量,彼此 之间不会共享,该类主要有三个方法:
1、 public T get() 返回巨变线程的变量。
2、 protected T initialValue() 返回局部变量的初始值;
3、 public void set(T value) 设置当前线程的局部变量;
其实看看TheadLocal的源码,我们可以发现他的思路,在类中有一个Map缓存,用于存储每个线程的局部变量,例:
package com.bin.duoxiancheng;import java.util.Collections;import java.util.HashMap;import java.util.Map;public class ThradLocal<T> { private Map<Runnable,T> values= Collections.synchronizedMap(new HashMap<Runnable,T>()); public T get(){ Thread cuThread = Thread.currentThread(); T o=values.get(cuThread); if(o ==null && !values.containsKey(cuThread)){ o=initValue(); values.put(cuThread, o); } return o; } public void set(T newValue){ values.put(Thread.currentThread(), newValue); } protected T initValue(){ return null; }}
以上程序仅仅实现了总体思路,细节上ThreadLocal类实现的更健壮,还保证了线程结束后,从Map缓存中删除这个线程的局部变量的引用。
- java 多线程简述
- java 多线程简述
- Java多线程/并发17、简述CAS 操作
- java多线程模式ThreadLocal原理简述及其使用详解
- java多线程模式ThreadLocal原理简述及其使用详解
- java多线程模式ThreadLocal原理简述及其使用详解
- C#多线程编程简述
- C#多线程编程简述
- C#多线程编程简述
- C#多线程编程简述
- 多线程之GCD简述
- C#多线程编程简述
- 多线程——简述多线程
- Java简述
- JAVA简述
- Java简述
- Java简述
- Java 简述
- 小波变换 完美通俗解读【转载】
- 网络视频监控P2P解决方案
- Vector作为返回值时出现的问题
- 利用格点证明恒等式
- .css("display","none");
- java 多线程简述
- [高考试卷]2012年四川卷
- 0-1背包问题
- 小波变换和motion信号处理(二)【转载】
- 给程序员介绍一些C++开源库
- QTableView中VerticalHeader嵌入checkbox
- MyEclipse 底色配置
- vbs 解码 unicode
- Android LayoutInflater原理分析,带你一步步深入了解View(一)