http://www.ibm.com/developerworks/library/j-jtp0730/index.html
学习编程的目的:解决问题/开发用户需要的功能
多线程可以解决什么问题?
将不同的子任务交给不同的线程去执行,提高系统响应速度(比单一线程执行所有任务更快)
从最简单的开始,如何创建线程!
run() : 封装线程要运行的任务,所以将线程要运行的代码封装到run()中即可!
方式一:创建一个类直接继承Thread类,并覆盖run()
方式二:创建一个类实现Runnable接口,实现run(),再将此类的实例传入Thread构造方法中
开启线程:
new Thread().start();
new Thread(MyTask).start(); [ MyTask implements Runnable ]
线程的几种状态切换
两种使用线程的方式:
package thread; /** * 多线程使用方式一: * 通过继承Thread类让子类成为线程类 * 再通过该线程类开启线程执行任务 * */ public class ThreadDemo_A extends Thread { public ThreadDemo_A(String threadName) { //将线程名称传入父类构造方法,设置父线程的name,子类再将此name继承下来 super(threadName); } @Override public void run() {//run()封装线程需要执行的任务 for(int i=0;i<100;i++) { //获取当前线程名称 System.out.println(Thread.currentThread().getName()); } } public static void main(String[] args) { //主线程 System.out.println("main() start"); ThreadDemo_A a = new ThreadDemo_A("work_11111"); a.start();//开启另外1个线程 new ThreadDemo_A("work_22222").start();//再开启1个线程 System.out.println("main() end"); } }
推荐的方式--->通过Runnable封装线程任务
package thread; /** * 多线程使用方式二: * 通过实现 Runnable 接口,在run()中定义需要由线程执行的任务 * 然后将子类实例传递到Thread构造函数中,让Thread类去开启线程执行子类中的run() * */ public class ThreadDemo_B implements Runnable { private String address; private String content; public ThreadDemo_B() {} public ThreadDemo_B(String address, String content) { this.address = address; this.content = content; } @Override public void run() { sendEmail();//线程任务:发送邮件 } private void sendEmail() { for(int i=0;i<100;i++) System.out.println(Thread.currentThread().getName()+"------->>>>"+"Send email to " + address +", content=" + content); } public static void main(String[] args) { ThreadDemo_B task1 = new ThreadDemo_B("[email protected]", "1111111"); ThreadDemo_B task2 = new ThreadDemo_B("[email protected]", "2222222"); new Thread(task1,"线程111111").start(); new Thread(task2,"线程222222").start(); } }
==========================================================================
单个线程操作资源不存在同步、异步。
同步:WC,进入后上锁,出来后打开锁。
通过同步(加锁)来解决多线程操作共享资源时的安全问题!
同步的弊端:效率低(操作共享资源之前,线程都需要判断锁,如果锁没有释放,是进入不了的,会一直等待锁的释放)。
什么时候需要使用同步?
多个线程操作共享资源
且必须使用同一个锁,才能实现多线程同步访问的效果!
不同情况下的同一 个锁:
同步代码块使用的锁:堆内存中的任何相同对象(内存地址相同,即同一个对象)。
Runnable中定义一个成员对象,如 Object locker = new Object();
synchronized(locker)使用的就是同一个锁;
同步函数使用的锁:this
静态同步函数使用的锁:该方法所属类的字节码对象 Ticket.class
Java提供的处理同步问题的办法:
同步代码块
public void doIt() { doSomething.... //操作共享资源的代码,使用同步代码库对其封装 synchronized (Task.class) { ... } doOtherthing... }
同步函数
//使用同步函数封装那些涉及共享资源操作的代码 public synchronized void doIt() { //操作共享资源的代码 code... }
由于同步中使用的锁可以是任何对象,所以wait(),notify(),notifyAll()被定义在Object中!
wait(),notify(),notifyAll()是绑定在同一个锁上相互进行通信的!
wait() : 某种不满足操作的情况下,线程让自己进入等待状态!locker.wait()
notify(): 当线程自己进入等待状态后,调用notify()唤醒线程池中的一个线程,只唤醒一个(可能唤醒的是本方线程,造成死锁)。locker.notify()
notifyAll() : 唤醒线程池中所有等待的线程,可以避免死锁的发生。locker.notifyAll()
package thread; /** * 简单的2个线程间交替执行示例 * 通过synchronized保证对资源的“原子操作”不被打断 * 通过线程通信实现切换运行 */ public class TwoThreadsCommunication { public static void main(String[] args) { new TwoThreadsCommunication().justDoIt(); } public void justDoIt() { final ResourceHandler r = new ResourceHandler(); new Thread(new Runnable() { public void run() { for(int i=0;i<100;i++) r.produce(); } }, "线程A").start(); new Thread(new Runnable() { public void run() { for(int i=0;i<100;i++) r.consume(); } }, "线程B").start(); } /** * 资源类 * * 将多线程操作的代码单独封装起来,然后在run()中通过对象来调用 * 将同步放到资源上,而不是在run()中进行控制,实现与具体线程的解耦 * 互斥不要放到线程上进行,而应该放到对资源类操作的方法中!!! */ class ResourceHandler { //状态变量在资源内部进行操作 private boolean full; //生产线程操作共享资源的方法 public synchronized void produce() { //notice: 这里用while,不要用if。可防止死锁! while(full) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=1;i<=10;i++) { System.out.println(Thread.currentThread().getName()+" run***" + i); } full = true; this.notify(); } //消费线程操作共享资源的方法 public synchronized void consume() { //notice: 这里用while,不要用if。可防止死锁! while(!full) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for(int j=1;j<=10;j++) { System.out.println(Thread.currentThread().getName()+" run******" + j); } full = false; this.notify(); } } }
多线程间共享资源的方式:
package thread; /** * 多线程共享资源的方式一: * * 操作共享资源的run()放到内部类中,然后操作外部类中定义的资源 * * 1个线程负责加,1个线程负责减 */ public class ShareDataStyle1 { private int sharedData = 100; public synchronized void add() { sharedData++; System.out.println(Thread.currentThread().getName()+" add:" + sharedData); } public synchronized void minus() { sharedData--; System.out.println(Thread.currentThread().getName()+" minus:" + sharedData); } //内部类访问外部类的共享资源 class Add implements Runnable { public void run() { while(true) add(); } } //内部类访问外部类的共享资源 class Minus implements Runnable { public void run() { while(true) minus(); } } public static void main(String[] args) { ShareDataStyle1 sharedDate = new ShareDataStyle1(); new Thread(sharedDate.new Add()).start(); new Thread(sharedDate.new Minus()).start(); } }
package thread; /** * 多线程共享资源的方式二: * * 将共享资源传递到不同的Runnable对象中 * * 1个线程负责加,1个线程负责减 */ public class ShareDataStyle2 { public static void main(String[] args) { SharedData data = new SharedData(); new Thread(new Add(data)).start(); new Thread(new Minus(data)).start(); } } class SharedData { private int sharedData = 100; public synchronized void add() { sharedData++; System.out.println(Thread.currentThread().getName()+" add:" + sharedData); } public synchronized void minus() { sharedData--; System.out.println(Thread.currentThread().getName()+" minus:" + sharedData); } } class Add implements Runnable { SharedData sharedData; public Add(SharedData sharedData) { this.sharedData = sharedData; } public void run() { while(true) sharedData.add(); } } class Minus implements Runnable { SharedData sharedData; public Minus(SharedData sharedData) { this.sharedData = sharedData; } public void run() { while(true) sharedData.minus(); } }
异步:游泳池,往里跳,往外出,互不干扰,这就是异步。
同步:解决多个线程操作共享资源发生安全隐患!
A线程在执行某个代码片段,B线程在A线程释放锁之前将被阻塞(同一个监视器下)。
而异步呢:多个线程以并行工作的方式协同完成整体任务,目的是更快的完成当前任务!
一个耗时较长的任务,如果可以被分割为几个小的部分,而且这几个部分没有前后依赖关系,则可以并行的执行(充分利用多CPU的能力),提高处理速度。
=======================================================================
几个容易混淆的概念
wait : 当前线程将释放CPU,释放锁
sleep : 当前线程将释放CPU,不释放锁
InterruptedException:
线程当前所处状态被清除时,如,本来线程A正在sleep(5000),当到2000ms时,突然其sleep状态被清除了,此时,线程A将继续进行执行。只是,这种情况发生之后,JVM会抛出InterruptedException 到该线程上,告诉你线程A非正常醒了,是否需要处理这种非正常的醒就看程序员如果处理该异常了!
需要理解的是:发生InterruptedException不代表当前线程结束了!
一般情况下,这种非正常状态改变发生后,直接让方法return即可!
调用线程t的interrupt()便可以让t线程发生InterruptedException,然后在catch块中改变程序执行控制标记,或者return都行,具体怎么弄就看情况了!
守护线程:setDeamon(boolean b)
当所有线程都是守护线程时,JVM就会自动退出。
如,设置一个守护线程专门管理系统的缓存:
Thread cacheThread = new Thread(new CacheManager());
cacheThread.setDeamon(true); //必须在开启线程之前进行设置
cacheThread.start();
线程结合点:join()
切入一个新的线程到当前环境,只有切入的线程执行完毕后,当前线程才会继续执行
Thread subThread = new SubThread();
subThread.join();//直到subThread执行完毕,当前线程才继续往下执行
线程优先级
Thread t1 = new Thread();
t1.setPriority(Thread.MAX_PRIORITY);//最高优先级:10
Thread t2 = new Thread();
t2.setPriority(Thread.MIN_PRIORITY);//最低优先级:1
Thread t3 = new Thread();
t3.setPriority(Thread.NORM_PRIORITY);//默认优先级:5
线程组ThreadGroup
将若干功能相似的线程放到一个组中,便于统一管理。如统一设置优先级等