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()方法能让当前 ...
随机推荐
- javascript中对数组对象的深度拷贝
在前端开发的某些逻辑中,经常需要对现有的js对象创建副本,避免污染原始数据的情况. 如果是简单的一维数组对象,可以使用两个原生方法: 1.splice var arr1 = ['a', 'b', 'c ...
- Golang包管理工具glide简介
Golang包管理工具glide简介 前言 Golang是一个十分有趣,简洁而有力的开发语言,用来开发并发/并行程序是一件很愉快的事情.在这里我感受到了其中一些好处: 没有少了许多代码格式风格的争论, ...
- 【CF809E】Surprise me!
题目 这是一道神仙题 看到这样一个鬼畜的柿子 \[\sum_{i=1}^n\sum_{j=1}^n\varphi(a_i\times a_j)\times dis(i,j)\] 又是树上距离又是\(\ ...
- HBase学习之路 (一)HBase基础介绍
产生背景 自 1970 年以来,关系数据库用于数据存储和维护有关问题的解决方案.大数据的出现后, 好多公司实现处理大数据并从中受益,并开始选择像 Hadoop 的解决方案.Hadoop 使用分 布式文 ...
- centos安装swoole
编译安装swoole: cd && wget https://github.com/swoole/swoole-src/archive/1.8.6-stable.tar.gz ...
- Ubuntu14.04更换阿里云源
步骤很简单一共三步,如下所示: 第一.备份源文件(防止万一) sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 第二.修改源文件(这里的源 ...
- PHP 回调函数call_user_func和 call_user_func_array()的理解
call_user_func(function,param); // 第一个参数是回调函数的函数名,第二个参数是参数 call_user_func函数类似于一种特别的调用函数的方法.其主要有以下的类型 ...
- 小白python语言基础
程序概念层级 表达式 语句包含表达式 包和模块 程序 变量与储存 变量:对象的别名 命名规则:字母.数字.下划线 只能以字母或下划线开头 能包含空格/中文 避免python关键字或函数名 简短 ...
- python 集合总结
''' 集合:1:他是无序的,他是不重复的. 2,他里面的元素必须是可哈希的. int str bool ()但是它本身是不可哈希的. 3,集合不能更改里面的元素. 4,集合可以求交集,并集,差集,反 ...
- 北京Uber优步司机奖励政策(4月23日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...