首页 > 代码库 > java之多线程二
java之多线程二
一、JDK5之后的Lock锁的概述和使用
package a; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SellTicket implements Runnable { //定义票 private int tickets = 100; //定义锁对象 private Lock lock = new ReentrantLock(); @Override public void run() { while(true){ try { //加锁 lock.lock(); if(tickets > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票"); } } finally { //释放锁 lock.unlock(); } } } }
package a; /** * 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上锁,在哪里释放锁 * 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。 * * public void lock() 获取锁 * public void unlock()释放锁 * */ public class SellTicketDemo { public static void main(String[] args) { SellTicket s = new SellTicket(); Thread t1 = new Thread(s,"窗口1"); Thread t2 = new Thread(s,"窗口2"); Thread t3 = new Thread(s,"窗口3"); t1.start(); t2.start(); t3.start(); } }
二、死锁
同步的弊端:
1.效率低,但是安全,我接收。
2.如果出现了同步弊端,就容易产生死锁问题。比如,现在有一个单人卫生间,一个人拉肚子进去了,然后将门给锁上。那么其他人如果要进去,只能等先前那个人出来,但是那人一直在里面就是不出来(痛并快乐着),那么其他人只能等。
死锁问题及其代码:
是指两个或者两个以上的线程在执行的过程中,因为争夺资源产生的一种互相等待现象。
package b; public class MyLock { //创建两个锁对象 public static final Object objA = new Object(); public static final Object objB = new Object(); }
package b; public class DieLock extends Thread{ private boolean flag ; public DieLock(boolean flag) { this.flag = flag; } @Override public void run() { if(flag){ synchronized (MyLock.objA) { System.out.println("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } } }else{ synchronized (MyLock.objB) { System.out.println("else objB"); synchronized (MyLock.objA) { System.out.println("else objA"); } } } } }
package b; public class DieLockDemo { public static void main(String[] args) { DieLock dl1 = new DieLock(true); DieLock dl2 = new DieLock(false); dl1.start(); dl2.start(); } }
运行结果1:
if objA
if objB
else objB
else objA
运行结果2:
if objA
else objB
运行结果3:
else objB
if objA
分析运行结果1:
当线程1调用start()和线程2调用start()方法时,线程1正好抢到CPU的执行权,那么此时会执行if语句,输出if objA,但是此时线程2依然没有抢到CPU的执行权,那么此时线程1继续执行,输出if objB。此时线程1执行完毕,线程2抢到CPU执行权,然后依次输出else objB,else objB。
分析运行结果2:
当线程1调用start()和线程2调用start()方法时,线程1正好抢到CPU的执行权,那么此时会执行if语句,输出if objA,锁对象objA没有释放,但是此时线程2抢到CPU的执行权,输出else ojbB,也没有释放锁对象objB,然后此时线程1抢到了CPU的执行权,那么要加锁时,却发现锁对象ojbB没有释放,就等待线程2释放锁对象ojbB,但是线程2就不释放锁对象ojbB。
分析运行结果3:
参考分析运行结果2.
三、线程间通信
针对同一个资源的操作有不同种类的线程。例如:卖票有进也有出。
package cn1; public class Student { String name;//学生姓名 int age;//学生年龄 }
package cn1; public class SetThread implements Runnable { @Override public void run() { Student s = new Student(); s.name = "林青霞"; s.age = 27; } }
package cn1; public class GetThread implements Runnable { @Override public void run() { Student s = new Student(); System.out.println("姓名是:"+s.name+",学生年龄:"+s.age); } }
package cn1; /** * 分析: * 资源类:Student * 设置学生数据:SetThread 生产者 * 获取学生数据:getThread 消费者 * 测试类:StudentDemo */ public class StudentDemo { public static void main(String[] args) { SetThread st = new SetThread(); GetThread gt = new GetThread(); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
姓名是:null,学生年龄:0
原因是我们再每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个。
如何实现?
在外界把这个数据创建出来,通过构造方法传递给其他的类。
package cn1; public class Student { String name;//学生姓名 int age;//学生年龄 }
package cn1; public class SetThread implements Runnable { private Student s; public SetThread(Student s){ this.s = s; } @Override public void run() { s.name = "林青霞"; s.age = 27; } }
package cn1; public class GetThread implements Runnable { private Student s; public GetThread(Student s){ this.s = s; } @Override public void run() { System.out.println("姓名是:"+s.name+",学生年龄:"+s.age); } }
package cn1; /** * 分析: * 资源类:Student * 设置学生数据:SetThread 生产者 * 获取学生数据:getThread 消费者 * 测试类:StudentDemo */ public class StudentDemo { public static void main(String[] args) { //创建资源 Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
姓名是:林青霞,学生年龄:27
但是也有可能结果是这样的:姓名是:null,学生年龄:0。或者是这样的结果::姓名是:null,学生年龄:27。
为什么?
姓名是:林青霞,学生年龄:27?
原因:是线程1先获得CPU的执行权(设置属性值),接着线程2获得CPU的执行权(获得属性值)。
姓名是:null,学生年龄:0?
原因:是线程2先获得CPU的执行权(取得属性值),但是此时线程1还没有取得CPU的执行权,所以呢,值就为默认值啦。
其他的原因就不去推测了,自己想想就知道啦??
package cn; public class Student { String name; int age; }
package cn; public class SetThread implements Runnable{ private Student s; private int x = 0; public SetThread(Student s){ this.s = s; } public void run() { while(true){ if(x % 2 == 0 ){ s.name = "林青霞"; s.age = 20; }else{ s.name = "东方不败"; s.age = 30; } x++; } } }
package cn; public class GetThread implements Runnable { private Student s ; public GetThread(Student s){ this.s = s; } @Override public void run() { while(true){ System.out.println("姓名:"+s.name+",年龄:"+s.age); } } }
package cn; /** * 为了数据的效果好一些,就加入了循环和判断,给出了不同的值,这个时候就产生了不同的问题 * 同一个数据出现了很多次:CPU的一点点时间片的执行权,就足够执行很多次 * 姓名和年龄不匹配:线程运行的随机性 * * */ public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
姓名:东方不败,年龄:20
姓名:东方不败,年龄:30
姓名:东方不败,年龄:20
姓名:林青霞,年龄:30
姓名:东方不败,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:30
姓名:东方不败,年龄:30
姓名:林青霞,年龄:30
姓名:林青霞,年龄:20
姓名:林青霞,年龄:30
姓名:东方不败,年龄:20
姓名:东方不败,年龄:20
我们发现同一个数据出现了多次以及姓名和年龄不匹配,原因已经在上面给出了。
那么如何解决呢?加锁
package cn; public class Student { String name; int age; }
package cn; public class SetThread implements Runnable{ private Student s; private int x = 0; public SetThread(Student s){ this.s = s; } public void run() { while(true){ synchronized (new Object()) { if(x % 2 == 0 ){ s.name = "林青霞"; s.age = 20; }else{ s.name = "东方不败"; s.age = 30; } x++; } } } }
package cn; public class GetThread implements Runnable { private Student s ; public GetThread(Student s){ this.s = s; } @Override public void run() { while(true){ synchronized (new Object()) { System.out.println("姓名:"+s.name+",年龄:"+s.age); } } } }
package cn; /** * 为了数据的效果好一些,就加入了循环和判断,给出了不同的值,这个时候就产生了不同的问题 * 同一个数据出现了很多次:CPU的一点点时间片的执行权,就足够执行很多次 * 姓名和年龄不匹配:线程运行的随机性 * * 线程安全问题: * 是否是多线程环境 是 * 是否有共享数据 是 * 是否有多条语句操作共享数据 是 * 解决方案: * 加锁 不同种类的线程都要加锁 */ public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
姓名:林青霞,年龄:20
姓名:林青霞,年龄:20
姓名:东方不败,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:林青霞,年龄:30
姓名:林青霞,年龄:30
姓名:林青霞,年龄:30
姓名:东方不败,年龄:30
姓名:林青霞,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:20
姓名:林青霞,年龄:20
姓名:林青霞,年龄:20
姓名:林青霞,年龄:30
姓名:林青霞,年龄:30
姓名:东方不败,年龄:20
姓名:林青霞,年龄:20
结果怎么还是这样?那是因为我们加的锁不一样。
package cn; public class Student { String name; int age; }
package cn; public class SetThread implements Runnable{ private Student s; private int x = 0; public SetThread(Student s){ this.s = s; } public void run() { while(true){ synchronized (s) { if(x % 2 == 0 ){ s.name = "林青霞"; s.age = 20; }else{ s.name = "东方不败"; s.age = 30; } x++; } } } }
package cn; public class GetThread implements Runnable { private Student s ; public GetThread(Student s){ this.s = s; } @Override public void run() { while(true){ synchronized (s) { System.out.println("姓名:"+s.name+",年龄:"+s.age); } } } }
package cn; /** * 为了数据的效果好一些,就加入了循环和判断,给出了不同的值,这个时候就产生了不同的问题 * 同一个数据出现了很多次:CPU的一点点时间片的执行权,就足够执行很多次 * 姓名和年龄不匹配:线程运行的随机性 * * 线程安全问题: * 是否是多线程环境 是 * 是否有共享数据 是 * 是否有多条语句操作共享数据 是 * 解决方案: * 加锁 不同种类的线程都要加锁,而且不同种类的线程必须加同一把锁 */ public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
姓名:东方不败,年龄:30
上述的仅仅解决了数据的匹配问题,但是每生产一条数据,就消费一条数据(生产者和消费者),即,当线程1设置了对象的属性,线程2就输出对象的属性。
package cn; public class Student { String name; int age; boolean flag;//默认没有数据 }
package cn; public class SetThread implements Runnable{ private Student s; private int x = 0; public SetThread(Student s){ this.s = s; } public void run() { while(true){ synchronized (s) { //如果有数据 if(s.flag){ try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{//数据没有数据 if(x % 2 == 0 ){ s.name = "林青霞"; s.age = 20; }else{ s.name = "东方不败"; s.age = 30; } x++; s.flag = true; s.notify(); } } } } }
package cn; public class GetThread implements Runnable { private Student s ; public GetThread(Student s){ this.s = s; } @Override public void run() { while(true){ synchronized (s) { //如果没有数据 if(!s.flag){ try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{//如果有数据 System.out.println("姓名:"+s.name+",年龄:"+s.age); s.flag = false; s.notify(); } } } } }
package cn; public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t2.start(); t1.start(); } }
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
姓名:林青霞,年龄:20
姓名:东方不败,年龄:30
等待唤醒的原理是什么呢?
等线程1和线程2启动的时候,假设此时线程2抢到CPU的执行权,那么执行线程2的run()方法,并执行加锁,但是此时s.flag为false,取反之后为true,然后执行等待,等待的同时释放锁,并且下次一旦其他线程唤醒线程1,会从这边继续执行。
那么此时线程2获取CPU的执行权,并执行加锁,这个时候s.flag是false,那么会进入else语句,又因为x=0,可以整除2,那么会执行s.name="林青霞",s.age="20",然后将flag变为true,然后执行唤醒,但是此时锁并没有释放,然后else执行完毕,释放锁。
之后,线程1和线程2继续争夺CPU的执行权,如果此时线程1获得CPU的执行权,并加锁,又因为s.flag=true,所以执行if语句里的代码,然后线程1等待,并释放锁,那么此时只有线程2来获得CPU的执行权了,那么线程2唤醒了,然后判断s.flag=true,那么就输出姓名:林青霞,年龄:20,将s.flag=false,然后通知其他线程,else执行完毕之后,释放锁,那么线程1和线程2继续争夺CPU的执行权。
四、线程的状态
五、线程组
java中使用ThreadGroup来表示线程组。它可以对一批线程进行分类管理,java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。public final ThreadGroup getThreadGroup()
我们也可以给线程设置分组:Thread(ThreadGroup group,Runnable target,String name)
package cn1; public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
package cn1; /** * 线程组:把多个线程组合到一起 * 它可以对一批线程进行分类管理,java允许程序直接对线程组控制 * * public ThreadGroup getThreadGroup()获取默认的线程组 * */ public class ThreadGroupDemo { public static void main(String[] args) { MyRunnable my = new MyRunnable(); Thread t1 = new Thread(my, "林青霞"); Thread t2 = new Thread(my, "东方不败"); ThreadGroup tg1 = t1.getThreadGroup(); System.out.println(tg1.getName()); ThreadGroup tg2 = t2.getThreadGroup(); System.out.println(tg2.getName()); System.out.println(Thread.currentThread().getThreadGroup().getName()); } }
main
main
main
通过结果我们知道,线程默认情况下属于main线程组。所有的线程都属于同一线程组,即main线程组。
package cn1; /** * 线程组:把多个线程组合到一起 * 它可以对一批线程进行分类管理,java允许程序直接对线程组控制 * * public ThreadGroup getThreadGroup()获取默认的线程组 * * * * */ public class ThreadGroupDemo { public static void main(String[] args) { method1(); method2(); } /** * 给线程设置分组 */ private static void method2() { ThreadGroup tg = new ThreadGroup("这是一个新的组"); MyRunnable my = new MyRunnable(); Thread t1 = new Thread(tg, my, "线程1"); Thread t2 = new Thread(tg, my, "线程2"); System.out.println("t1的线程组名称为:"+t1.getThreadGroup().getName()); System.out.println("t2的线程组名称为:"+t2.getThreadGroup().getName()); } /** * 获取默认的线程组 */ private static void method1() { MyRunnable my = new MyRunnable(); Thread t1 = new Thread(my, "林青霞"); Thread t2 = new Thread(my, "东方不败"); ThreadGroup tg1 = t1.getThreadGroup(); System.out.println(tg1.getName()); ThreadGroup tg2 = t2.getThreadGroup(); System.out.println(tg2.getName()); System.out.println(Thread.currentThread().getThreadGroup().getName()); } }
main
main
main
t1的线程组名称为:这是一个新的组
t2的线程组名称为:这是一个新的组
六、生产者和消费者值等待唤醒机制代码的优化
package cn2; public class Student { private String name; private int age; private boolean flag; public synchronized void set(String name,int age){ //如果有数据 if(this.flag){ //等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //设置数据 this.name = name; this.age = age; this.flag = true; this.notify();//唤醒线程 } public synchronized void get(){ //如果没有数据,就等待 if(!this.flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(this.name+":"+this.age); this.flag = false; this.notify();////唤醒线程 } }
package cn2; public class SetThread implements Runnable { private Student s; private int x = 0; public SetThread(Student s){ this.s = s; } @Override public void run() { while(true){ if(x % 2 == 0){ s.set("林青霞", 20); }else{ s.set("东方不败", 30); } x++; } } }
package cn2; public class GetThread implements Runnable{ private Student s; public GetThread(Student s){ this.s = s; } @Override public void run() { while(true){ s.get(); } } }
package cn2; public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
七、线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5之后,java内置支持线程池。
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ExecutorService newFixedThreadPool(int nThreads)
这些方法的返回值是ExecutorService对象,该对象表示一个线程池么可以执行Runnable或Callable对象代表的线程。它提供了如下方法
<T> Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)
package cn2; public class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"--"+i); } } }
package cn2; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorsDemo { public static void main(String[] args) { //创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); MyThread my = new MyThread(); //创建Runnable实例 Thread t1 = new Thread(my); Thread t2 = new Thread(my); //提交Runnable实例 pool.submit(t1); pool.submit(t2); //关闭线程池 pool.shutdown(); } }
八、多线程实现方式三
package cn3; import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { private int from; private int to; public MyCallable(int from ,int to){ this.from = from; this.to = to; } @Override public Integer call() throws Exception { int sum = 0; for (int i = from; i <= to; i++) { sum += from + i; } return sum; } }
package cn3; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Test { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService pool = Executors.newFixedThreadPool(2); Future<Integer> f1 = pool.submit(new MyCallable(0, 100)); Future<Integer> f2 = pool.submit(new MyCallable(0, 50)); Integer t1 = f1.get(); Integer t2 = f2.get(); System.out.println(t1); System.out.println(t2); pool.shutdown(); } }
九、定时器
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在java中,可以通过Timer和TimerTask类来实现定义调度的功能。
开发中:Quartz是一个完全由java编写的开源调度框架。
package cn4; import java.util.Timer; import java.util.TimerTask; class MyTimerTask extends TimerTask{ @Override public void run() { System.out.println("爆炸"); } } public class TimerTest { public static void main(String[] args) { Timer t = new Timer(); t.schedule(new MyTimerTask(), 3000); } }
如何在3秒执行后终止定时器呢?
package cn4; import java.util.Timer; import java.util.TimerTask; class MyTimerTask extends TimerTask{ private Timer t; public MyTimerTask(Timer t) { this.t = t; } @Override public void run() { System.out.println("爆炸"); t.cancel();//结束定时器 } } public class TimerTest { public static void main(String[] args) { Timer t = new Timer(); t.schedule(new MyTimerTask(t), 3000); } }
package cn5; import java.io.File; import java.util.Timer; import java.util.TimerTask; /** * 定时删除文件夹 * */ class DeleteFolder extends TimerTask{ private Timer t ; public DeleteFolder(Timer t){ this.t = t; } @Override public void run() { File srcFolder = new File("demo"); deleteFolder(srcFolder); t.cancel(); } //递归删除目录 private void deleteFolder(File srcFolder) { File[] fileArray = srcFolder.listFiles(); if(fileArray != null){ for (File file : fileArray) { if(file.isDirectory()){ deleteFolder(file); }else{ file.delete(); } } srcFolder.delete(); } } } public class TimerTest { public static void main(String[] args) { Timer t = new Timer(); t.schedule(new DeleteFolder(t), 3000); } }
本文出自 “11831428” 博客,请务必保留此出处http://11841428.blog.51cto.com/11831428/1873245
java之多线程二