java多线程相关代码
2019-02-25 16:11:51来源:博客园 阅读 ()
1.创建线程的三种方式
使用Thread
package com.wpbxx.test; //1.自定义一个类,继承java.lang包下的Thread类 class MyThread extends Thread{ //2.重写run方法 @Override public void run() { //3.将要在线程中执行的代码编写在run方法中 for(int i = 0; i < 1000; i++) { System.out.println("wpb"); } } } public class helloworld { public static void main(String[] args) { //4.创建上面自定义类的对象 MyThread mt = new MyThread(); //5.调用start方法启动线程 mt.start(); for(int i = 0; i< 1000; i++) { System.out.println("xx"); } } }
使用Runnable
package com.wpbxx.test; //1.自定义一个类实现java.lang包下的Runnable接口 class MyRunnable implements Runnable{ //2.重写run方法 @Override public void run() { //3.将要在线程中执行的代码编写在run方法中 for(int i = 0; i < 1000; i++) { System.out.println("wpb"); } } } public class helloworld { public static void main(String[] args) { //4.创建上面自定义类的对象 MyRunnable mr = new MyRunnable(); //5.创建Thread对象并将上面自定义类的对象作为参数传递给Thread的构造方法 Thread t = new Thread(mr); //6.调用start方法启动线程 t.start(); for(int i = 0; i < 1000; i++) { System.out.println("xx"); } } }
使用Callable接口创建的线程会获得一个返回值并且可以声明异常。
优点: 可以获取返回值 可以抛出异常
线程池
线程池是初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时直接去这个线程集合中获取,而不是创建一个线程。任务执行结束后,线程回到池子中等待下一次的分配。
线程池的作用
解决创建单个线程耗费时间和资源的问题。
package com.wpbxx.test; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; //1.自定义一个类实现java.util.concurrent包下的Callable接口 class MyCallable implements Callable<Integer>{ private int count; public MyCallable(int count) { this.count = count; } //2.重写call方法 @Override public Integer call() throws Exception{ //3.将要在线程中执行的代码编写在call方法中 for(int i = 0; i < 100; i++) { count++; } return count; } } public class helloworld { public static void main(String[] args) { //4.创建ExecutorService线程池 里面为线程的数量 ExecutorService es = Executors.newFixedThreadPool(2); ////创建一个线程池,里面的线程会根据任务数量进行添加 //ExecutorService es = Executors.newCachedThreadPool(); //5.将自定义类的对象放入线程池里面 Future<Integer> f1= es.submit(new MyCallable(5)); Future<Integer> f2 = es.submit(new MyCallable(3)); try { //6.获取线程的返回结果 System.out.println(f1.get()); System.out.println(f2.get()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } //7.关闭线程池,不再接收新的线程,未执行完的线程不会被关闭 es.shutdown(); } }
继承Thread
优点:可以直接使用Thread类中的方法,代码简单
缺点:继承Thread类之后就不能继承其他的类
实现Runnable接口
优点:即时自定义类已经有父类了也不受影响,因为可以实现多个接口
缺点: 在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法
实现Callable接口
优点:可以获取返回值,可以抛出异常
缺点:代码编写较为复杂
package com.wpbxx.test; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; //简易写法 使用匿名内部类创建多线程 public class helloworld { public static void main(String[] args) throws InterruptedException, ExecutionException { new Thread() { public void run() { for(int i = 0; i < 1000; i++) { System.out.println("wpb"); } } }.start(); new Thread(new Runnable() { public void run() { for(int i = 0; i< 1000; i++) { System.out.println("xx"); } } }).start(); ExecutorService exec = Executors.newCachedThreadPool(); Future<Integer> result = exec.submit(new Callable<Integer>() { @Override public Integer call() throws Exception{ return 1024; } }); System.out.println(result.get()); } }
Thread设置线程的名字
方法一
new Thread("马化腾") { //通过构造方法给name赋值 public void run() { System.out.println("我是" + this.getName() + ",来腾讯工作吧 "); } }.start();
方法二
new Thread() { public void run() { this.setName("马化腾"); //调用setName System.out.println("我是" + this.getName() + ",来腾讯啊"); } }.start();
使用Thread.currentThread() 获得正在运行的线程
可以这样改变Runnable中线程名字
package com.wpbxx.test; public class helloworld { public static void main(String[] args) { new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName()); Thread.currentThread().setName("wpb"); System.out.println(Thread.currentThread().getName()); } }).start(); } }
线程睡眠
Thread中的sleep方法可以使当前线程睡眠,线程睡眠后,里面的任务不会执行,待睡眠时间过后会自动苏醒,从而继续执行任务。
Thread.sleep(1000); //让当前线程睡眠1秒
线程的优先级
setPriority()方法接收一个int类型的参数,通过这个参数可以指定线程的优先级,取值范围是整数1~10,优先级随着数字的增大而增强。 但并不是一定执行优先级高的执行完之后 才执行别的
package com.wpbxx.test; public class helloworld { public static void main(String[] args) { Thread t1 = new Thread() { public void run() { for(int i = 0; i<100; i++) { System.out.println("wpb"); } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 100; i++) { System.out.println("1024"); } } }; t1.setPriority(10); t2.setPriority(0); t1.start(); t2.start(); } }
唤醒睡眠中的线程
t1.interrupt();
用interrupt方法会抛出一个InterruptedException的异常。
同步方法
package com.wpbxx.test; public class helloworld { public static void main(String[] args) { Task tk = new Task(); Thread t1 = new Thread() { public void run() { tk.changeNum(true); } }; Thread t2 = new Thread() { public void run() { tk.changeNum(false); } }; t1.start(); t2.start(); } } class Task{ private int num = 0; public void changeNum(boolean flag) { if(flag) { num = 99; System.out.println(Thread.currentThread().getName() + "-------" + "begin"); System.out.println(Thread.currentThread().getName() + "-------" + num); System.out.println(Thread.currentThread().getName() + "-------" + "end"); }else { num = 22; System.out.println(Thread.currentThread().getName() + "-------" + "begin"); System.out.println(Thread.currentThread().getName() + "-------" + num); System.out.println(Thread.currentThread().getName() + "-------" + "end"); } } }
正常情况下应该打印出一个88一个66,可是上面却两个线程打印出的两个66,这样就出现了线程安全的问题,出现这个问题的原因是成员变量存储在堆内存中,两个线程共享堆内存,即两个线程可以对同一个num进行修改。
程序执行分析:
cpu执行t1线程,将num修改为88,之后cpu开始执行t2线程,将num修改为66,打印出66,cpu开始执行t1线程,打印num的值,此时num的值是66。
在方法上加入synchronized关键字,这样在执行多个线程时看哪个线程先执行这个方法,假设有t1,t2,t3三个线程中都调用了changeNum方法,t1线程先执行了这个方法,那么t1会先在Task对象上面加锁,加锁后,别的线程就无法执行当前Task对象上的changeNum方法,直到t1执行结束changeNum方法之后,t2,t3中的一个线程才可以执行这个方法,这就保证了在某个时间段内只有一个线程执行changeNum方法,解决了线程安全问题。
注意:synchronized锁住的是当前对象,如果t1线程和t2线程里面是不同的对象,则不需要同步,因为不会发生线程安全问题
public synchronized void changeNum(boolean flag) 加这一句就ok了
也可以对需要互斥访问的代码块加上synchronized
public void changeNum(boolean flag){ try { Thread.sleep(3000); System.out.println("执行一个耗时较长的任务"); } catch (InterruptedException e) { e.printStackTrace(); } //这个方法中,需要同步的代码块是这部分,而上面耗时操作的代码,不涉及到线程安全问题,所以不需要同步 synchronized(obj){ if(flag){ num = 88; System.out.println(Thread.currentThread().getName() + "========== begin"); System.out.println(Thread.currentThread().getName() + "==========" + num); System.out.println(Thread.currentThread().getName() + "========== end"); }else{ num = 66; System.out.println(Thread.currentThread().getName() + "========== begin"); System.out.println(Thread.currentThread().getName() + "==========" + num); System.out.println(Thread.currentThread().getName() + "========== end"); } } }
死锁
发生死锁原因就是两个或多个线程都在等待对方释放锁导致,下面通过代码来演示一下死锁情况。
package com.wpbxx.test; public class helloworld { private static Object obj1 = new Object(); private static Object obj2 = new Object(); public static void main(String[] args) { new Thread() { public void run() { synchronized(obj1) { System.out.println(this.getName()); synchronized(obj2) { System.out.println(this.getName()); } } } }.start(); new Thread() { public void run() { synchronized(obj2) { System.out.println(this.getName()); synchronized(obj1) { System.out.println(this.getName()); } } } }.start(); } }
volatile关键字
package com.wpbxx.test; public class helloworld { public static void main(String[] args) throws InterruptedException { Task task = new Task(); Thread t1 = new Thread(task); t1.start(); Thread.sleep(100); task.setFlag(false); } } class Task implements Runnable{ private boolean flag = true; public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public void run() { while(flag) { System.out.println("while循环"); } System.out.println("循环结束"); } }
上面程序中在64位的机器上以server模式运行时,有可能会出现死循环的现象。
JVM的运行可以分为下面两种模式:
- client:启动快,运行后性能不如server模式,一般运行时默认是client模式
- server:启动慢,运行后性能比client模式好。
在eclipse中可以通过配置来使用server模式,右键—>run as—>run configurations。写上-server。然后点击run即可
上面程序出现问题的原因这样的,虽然在主线程中将flag的设置为false,但是jvm为了提升效率,t1线程一直在私有内存中获取flag的值,而私有内存中的flag值并没有被改变,所以导致死循环的发生。
使用volatile修饰flag解决上面问题:
volatile private boolean flag = true;
将flag声明为volatile后,t1线程会从公共的内存中访问flag的值,这样在主线程将flag设置为false后,t1线程中的循环就会结束了。
注意:volatile只能修饰变量,不能修饰方法
原子性和非原子性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
非原子性:不符合原子性的就是非原子性
int x = 1024; //原子性 int y = x; //cpu先去内存中读取x的值,读取后在为y进行赋值,在读取后给y赋值前的这段时间可能会切换到其他线程上面。 x++; //包含了三个操作,先读取x的值,然后进行加1操作,最后写入新的值,在这三个操作的间隙可能会切换到其他线程上面。 x = x + 1; //同上
volatile是非原子性的。
synchronized是原子性的。
TimerTask
TimerTask是一个实现了Runnable接口的抽象类,需要编写一个类继承TimerTask类,将要在定时任务执行的代码编写在run方法中。
要想执行定时任务,需要创建Timer的对象并调用里面的schedule方法,在Timer类中有多个重载的schedule方法,这里咱们使用这个:
schedule(TimerTask task, Date firstTime, long period);
package com.wpbxx.test; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Timer; import java.util.TimerTask; public class helloworld { public static void main(String[] args) throws InterruptedException, ParseException { Timer t = new Timer(); t.schedule(new MyTimerTask(), new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS").parse("2017-07-03 18:09:00 000"), 5000); } } class MyTimerTask extends TimerTask{ @Override public void run() { System.out.println("wpbxx"); } }
线程之间的通信
多线程环境下CPU会随机的在线程之间进行切换,如果想让两个线程有规律的去执行,那就需要两个线程之间进行通信,在Object类中的两个方法wait和notify可以实现通信。
wait方法可以使当前线程进入到等待状态,在没有被唤醒的情况下,线程会一直保持等待状态。
notify方法可以随机唤醒单个在等待状态下的线程。
利用wait 和notify 进行交替打印
package com.wpbxx.test; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Timer; import java.util.TimerTask; public class helloworld { public static void main(String[] args) throws InterruptedException, ParseException { Print p = new Print(); Thread t1 = new Thread() { public void run() { while(true) { p.print1(); } } }; Thread t2 = new Thread() { public void run() { while(true) { p.print2(); } } }; t1.start(); t2.start(); } } class Print{ private int flag = 1; public void print1() { synchronized(this) { if(flag != 1) { try { this.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("wpb"); flag = 2; this.notify(); } } public void print2() { synchronized(this) { if(flag != 2) { try { this.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("xx"); flag = 1; this.notify(); } } }
但这样如果是三个线程以上的 就不行, 可能出现死锁了
因为是随机唤醒一个等待的线程, 假设一线程 进行玩后 随即唤醒一个线程,并把flag = 2, 但这时唤醒了线程3 就会一直等待
notifyAll() 为唤醒所有的线程
package com.wpbxx.test; /** * 三个(三个以上)线程之间的通信 * */ public class helloworld { public static void main(String[] args) { Print1 p = new Print1(); Thread t1 = new Thread(){ public void run(){ while(true){ p.print1(); } } }; Thread t2 = new Thread(){ public void run(){ while(true){ p.print2(); } } }; Thread t3 = new Thread(){ public void run(){ while(true){ p.print3(); } } }; t1.start(); t2.start(); t3.start(); } } class Print1{ private int flag = 1; public void print1(){ synchronized(this){ while(flag != 1){ try { //让当前线程进入等待状态 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("monkey"); flag = 2; //唤醒所有等待的线程 this.notifyAll(); } } public void print2(){ synchronized(this){ while(flag != 2){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("1024"); flag = 3; this.notifyAll(); } } public void print3(){ synchronized(this){ while(flag != 3){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("888"); flag = 1; this.notifyAll(); } } }
这样就可以实现三个线程的交替打印, 但会有问题 就是唤醒所有的线程 开销太大。
上面notify() 或者 notifyAll() 并不能唤醒指定的线程,所以多出了 互斥锁
新增了 ReenTrantLock类 和 Condition接口 来替换 synchronized关键字 和 wait、notify 方法。
ReenTrantLock类 和Condition接口 都在java.util.concurrent.locks包下。
可以使用 ReentrantLock类中 的 lock方法 和 unlock方法 进行上锁和解锁,用来替代synchronized关键字。
Condition接口中的await方法和signal方法用来让线程等待和唤醒指定线程。用来替代wait方法和notify方法。
如 还是循环打印东西
package com.wpbxx.test; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class helloworld { public static void main(String[] args) throws InterruptedException { Print p = new Print(); Thread t1 = new Thread() { public void run() { while(true) { p.print1(); } } }; Thread t2 = new Thread() { public void run() { while(true) { p.print2(); } } }; Thread t3 = new Thread() { public void run() { while(true) { p.print3(); } } }; t1.start(); t2.start(); t3.start(); } } class Print{ private ReentrantLock r = new ReentrantLock(); private Condition c1 = r.newCondition(); private Condition c2 = r.newCondition(); private Condition c3 = r.newCondition(); private int flag = 1; public void print1() { r.lock(); while(flag != 1) { try { c1.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("wpb1"); flag = 2; c2.signal(); r.unlock(); } public void print2() { r.lock(); while(flag != 2) { try { c2.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("wpb2"); flag = 3; c3.signal(); r.unlock(); } public void print3() { r.lock(); while(flag != 3) { try { c3.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("wpb3"); flag = 1; c1.signal(); r.unlock(); } }
以后再补充
原文链接:https://www.cnblogs.com/wpbing/p/10429695.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash