java多线程相关代码
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(); } }
以后再补充
上一篇: 使用Asp.Net Core MVC 开发项目实践[第一篇:项目结构说明]
下一篇: 字符串和集合