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();
} }
以后再补充
java多线程相关代码的更多相关文章
- Java多线程相关的
很多小伙伴在学习Java的时候,总是感觉Java多线程在实际的业务中很少使用,以至于不会花太多的时间去学习,技术债不断累积!等到了一定程度的时候对于与Java多线程相关的东西就很难理解,今天需要探讨的 ...
- Java多线程相关面试题及答案-整理
1.什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速.比如,如果一个线程完成 ...
- Java[3] Java多线程相关资料
Java多线程: http://www.uml.org.cn/j2ee/201509093.asp
- Java多线程同步代码块
/*多线程的安全问题1.为什么会出现安全问题?因为程序在运行时,会出现一个线程在判断条件满足后,具备了执行资格,但没有运行代码后一个线程也判断了条件,也具备了执行资格,后一个线程运行了代码,但这时候, ...
- 2018/1/15 JAVA多线程相关
本文不说synchronized相关,它就是JAVA的一个保留关键字,jdk自己实现了它,但说真的,可应用场景真的少,相比lock接口,它还是被淘汰好吧; 首先,说说lock接口,lock接口是一个工 ...
- Java多线程相关的常用接口
Runnable 是一个接口,里面只声明了一个方法run();返回值为void所以无法拿到执行完的结果.只能通过共享变量或者线程通信来搞定.Future就是对具体的Runable或者Callable任 ...
- Java多线程相关知识
1)wait() notify() sleep() sleep是Thread类的函数,wait和notify是Object的函数. sleep的时候keep对象锁,wait的时候release 对 ...
- 复利计算C语言转java的相关代码
static void principal()// 计算本金 { int N, m; double i, F, P; System.out.printf("复利终值:"); F = ...
- Java多线程相关的API方法以及作用摘要
wait() 会让当前运行线程 阻塞,并释放对应的对象锁, 一般由当前线程持有的对象锁调用 x.wait(): 当前线程必须拥有此对象的monitor(即锁),才能调用此对象的wait()方法能让当前 ...
随机推荐
- python第八课——random模块的使用
2.2.如何获取随机整数值? 引入random模块的使用 randint(a,b)函数:作用:返回给程序一个[a,b]范围内的随机整数注意:含头含尾闭区间 思路步骤: 第一步:导入random模块到相 ...
- CSS3新特性2D、3D效果讲解
希望这篇博客可以对你有所帮助,如果有什么技术上的问题,希望我们可以做进一步的交流,如果你觉得我哪里阐述的不正确或者你有更好的更透彻的理解,也可以联系我,我在这里随时等着你. 对于css/html是每个 ...
- [SDOI2014]LIS
这道题还是非常好的 首先第一问可以让我们联想到某网络流二十四题里的一道题,发现建图方式应该和这道题差不多啊 所以首先跑一遍\(dp\),求出\(dp[i]\)表示\(i\)位置结束的\(LIS\)长度 ...
- 【转】Android SDK,ADT,API 版本的对应关系
写对应关系之前,先了解一下几个名字的含义. 一. Android ADT: 按照官方网站的开发介绍:Android Development Tools (ADT) is a plugin for th ...
- HBase学习之路 (一)HBase基础介绍
产生背景 自 1970 年以来,关系数据库用于数据存储和维护有关问题的解决方案.大数据的出现后, 好多公司实现处理大数据并从中受益,并开始选择像 Hadoop 的解决方案.Hadoop 使用分 布式文 ...
- React-Native 之 index.android.bundle
问题: index.android.bundle 这个bug 我相信很少同学会遇到,然而就是这个问题,困扰了我跟我的同事多天, 各种方法处理: 进入 android 目录 ./gradlew c ...
- 课后实践之mybash20155314
课后实践之mybash 实践要求 加分题-mybash的实现 使用fork,exec,wait实现mybash 写出伪代码,产品代码和测试代码 发表知识理解,实现过程和问题解决的博客(包含代码托管链接 ...
- DOS命令行简单用法
DOS命令行简单用法 基本用法 1.cd(回车)从当前目录切回到根目录. 2.dir(回车)列出当前目录下的所有文件. 3.md kkk(回车)在当前目录下创建一个名称为kkk的文件夹. 4.rd k ...
- TensorFlow入门:线性回归
随机.mini-batch.batch(见最后解释) 在每个 epoch 送入单个数据点.这被称为随机梯度下降(stochastic gradient descent).我们也可以在每个 epoch ...
- 关于C#的静态类和静态构造函数
静态构造函数是C#的一个新特性,其实好像很少用到.不过当我们想初始化一些静态变量的时候就需要用到它了.这个构造函数是属于类的,而不是属于哪里实例的,就是说这个构造函数只会被执行一次.也就是在创建第一个 ...