多线程--Java
多线程:
1.进程和线程
进程是资源分配的最小单位,线程是CPU调度的最小单位。
- 每个进程的创建都需要系统为其开辟资源内存空间,并发执行的程序在执行过程中分配和管理资源的基本单位,速度和销毁也较慢。进程和进程之间一般没有关联性。 System.Diagnostics.Process 进程的创建类,属于系统资源。
- 线程是运行在进程中的可认为是小子进程,线程共享调用进程中的资源内存空间,多个线程同时运行就出现并发线程,线程的调度顺序如果不影响结果且较占用时间,则可使用多线程。
- 服务是管理和协调守护进程的。
- 任务是完成某项操作的活动。可以是进程,可以是线程,可大可小。
比如Tomcat的webapp下有两个程序project1和project2.这两个程序默认各自会有一个进程,但一个程序也可开辟多个进程,一般各进程间没有资源的互相调用,且可独立移出时才开辟(浏览器的新建标签页,程序中消息推送功能)。而每次用户的请求服务都会生成一个主线程,如果后台java中创建多个Thread 子线程,就会出现并发情况,这些子线程共享进程的资源空间,速度快,生成销毁也快。
2. 并行和并发
并行是多条线路同时进行。
并发是多个请求同时抢占一个资源。
以打饭为例:并行,就是开了多个窗口,这些人可以排成好几个队进行打饭操作。并发,一个窗口多个人在等待,这些人没有先后顺序,谁先到cpu口,谁先执行。不一定会按照ABC顺序进行。
3.synchronized关键字
线程的调度顺序影响其结果,则需要同步锁(例如:银行系统中的存取,必须先存后取,不能存取都开线程后,让任意优先)。每一个对象都会有一个默认的隐形锁,单线程不起作用。多线程调用时,就会查找synchronized是否起作用。对象加锁和解锁是很耗性能的,因此慎用,当然安全比性能优先。
Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。
wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。
当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
锁定代码块,会锁定传入类对这部分代码的访问控制, 以以下代码为例:
1 package jquery.test.interview;
2
3 /**
4 * 银行存取业务
5 *
6 * @author DennyZhao
7 * @date 2017年6月29日
8 * @version 1.0
9 */
10 public class BankAccess {
11
12 private int money;
13
14 /**
15 * 存入钱
16 * @param store
17 */
18 public void saveMoney(int store){
19 money+=store;
20 }
21
22 /**
23 * 减去手续费
24 */
25 public void getoutMoney(int out){
26 money-=out;
27 }
28
29 /**
30 * 查看钱
31 */
32 public int searchMoney(){
33 return money;
34 }
35 }
36
37
38 -----------------------------银行操作类
39 package jquery.test.interview;
40
41 public class BankTreader implements Runnable {
42 /** 创建银行操作类 **/
43 private BankAccess bank;
44
45 private int saveMoney;
46
47 public int getSaveMoney() {
48 return saveMoney;
49 }
50
51 public void setSaveMoney(int saveMoney) {
52 this.saveMoney = saveMoney;
53 }
54
55 /**
56 * 构造类
57 * @param bank
58 */
59 public BankTreader(BankAccess bank){
60 this.bank = bank;
61 }
62
63 public void handlerBank(){
64 synchronized(bank){
65 bank.saveMoney(saveMoney); //存入
66 bank.getoutMoney((int)Math.round(bank.searchMoney()*0.01));//收取总额手续费,当然实际银行不会这样
67 System.out.println("---------" + bank.searchMoney()); //查看
68 }
69 }
70 @Override
71 public void run() {
72 this.handlerBank();
73 }
74
75 --------------------------------------------
76 测试方法:
77 public static void main(String[] args) {
78 BankAccess bank = new BankAccess();
79 BankTreader handler = new BankTreader(bank);
80 handler.setSaveMoney(500);
81 Thread t1 = new Thread(handler,"t1");
82 handler.setSaveMoney(300);
83 Thread t2 = new Thread(handler,"t2");
84 t1.start();
85 t2.start();
86 }
87
88 结果:
89 ---------297
90 ---------591
其中通过对银行的存入,手续费,查看3个方法绑定到一个同步块中,实现多个方法绑定的作用,而不影响多线程单独调用各个方法。
如果synchronized代码块对象是this,就相当于锁定了handler对象,而非bank对象。
如果synchronized代码块对象是handler的Class类,则对所有的handler对象其作用,就是不论你new 多个handler还是一样可以控制同步化(例如下面结果)。
public static void main(String[] args) {
BankAccess bank = new BankAccess();
BankTreader handler = new BankTreader(bank);
BankTreader handler2 = new BankTreader(bank);
handler.setSaveMoney(500);
Thread t1 = new Thread(handler,"t1");
handler2.setSaveMoney(300);
Thread t2 = new Thread(handler2,"t2");
t1.start();
t2.start();
}
测试结果:
---------495
---------787
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public synchronized test(){*****} 锁住了这个对象即this,等同于 代码块 synchronized(this){*****},但不影响对其它代码块的调用。
因同步方法同步的是对象,如果有两个对象实例则不会影响。因此要注意避免在多线程中创建多个实例。
以下这种例子,就相当于没有加同步代码块。
/**
*
*
*/
public class ClassA {
public synchronized void setiTest(int iTest) {
ClassA a = new ClassA();
ClassA b = new ClassA();
Thread t1= new Thread(){
@Override
public void run() {
a.setiTest(555555555);
System.out.println("-----");
}
};
Thread t1= new Thread(){
@Override
public void run() {
b.setiTest(555555555);
System.out.println("-----");
}
};
3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
因为静态方法属于类对象,而非实例对象。因此静态的synchronized方法作用域是所有的实例对应的类。也就是整个过程只会有一个口,不论你new多少对象。
静态方法不加同步块:
public static int staticMoney;
private static int sum;
public static void getStaticMoney(){
sum+=staticMoney;
System.out.println(sum);
}
测试口:
public static void main(String[] args) {
BankAccess bank = new BankAccess();
BankTreader handler = new BankTreader(bank);
BankTreader handler2 = new BankTreader(bank);
BankTreader handler3 = new BankTreader(bank);
handler.staticMoney = 100;
Thread t1 = new Thread(handler,"t1");
Thread t2 = new Thread(handler2,"t2");
Thread t3 = new Thread(handler3,"t3");
t1.start();
t2.start();
t3.start();
try {
t1.sleep(100);
t2.sleep(50);
t3.sleep(0);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
加sleep防止线程同时进行,就相当于一个执行完后,执行另一个。
结果 :
200
200
200
去掉sleep进行:
结果:
运行1次
100
300
200
运行 2次
100
200
100
运行 3次
200
200
300。。。。。。
会出现结果未知,因为static的1个方法同时被多个线程同时调用尽管是多个对象,但类只有一个,就并发安全问题。
加上synchronized,多次运行结果:
100
200
300
这样就保证了多个线程在运行这个方法时,变为单线程。
4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
public void handlerBank(){
synchronized(BankAccess.class){
System.out.println(System.currentTimeMillis() + "---A--" + bank.searchMoney());
//拖延时间
for(int i=0; i <8000000;i++){
int b= i;
}
bank.saveMoney(saveMoney); //存入
bank.getoutMoney((int)Math.round(bank.searchMoney()*0.01));//收取总额手续费,当然实际银行不会这样
System.out.println(System.currentTimeMillis() + "----B-----" + bank.searchMoney()); //查看
}
}
测试方法:
public static void main(String[] args) {
BankAccess bank = new BankAccess();--构造多个实例
BankAccess bank2 = new BankAccess();
BankAccess bank3 = new BankAccess();
bank.saveMoney(1000);
bank2.saveMoney(2000);
bank3.saveMoney(4000);
BankTreader handler = new BankTreader(bank);--构造多个操作类
Thread t1 = new Thread(handler,"t1");
BankTreader handler2 = new BankTreader(bank2);
Thread t2 = new Thread(handler2,"t2");
BankTreader handler3 = new BankTreader(bank3);
Thread t3 = new Thread(handler3,"t3");
try {
t2.sleep(10);
t3.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t1.start();
t2.start();
t3.start();
}
结果:
1498732805187---A--1000
1498732805193----B-----990
1498732805193---A--4000
1498732805196----B-----3960
1498732805196---A--2000
1498732805200----B-----1980
结果分析:
对Bank类的操作还是变为线性,当t1,执行时,因执行时间长,因此当t2,t3在不同时间段醒来后,t1中的BankAccess依然没执行完,当执行完后,t3先抢到锁,因此t2,继续等待。
public static void main(String[] args) {
BankAccess bank = new BankAccess();
BankAccess bank2 = new BankAccess();
BankAccess bank3 = new BankAccess();
bank.saveMoney(1000);
bank2.saveMoney(2000);
bank3.saveMoney(4000);
BankTreader handler = new BankTreader(bank);
Thread t1 = new Thread(handler,"t1");
handler.setBank(bank2);
Thread t2 = new Thread(handler,"t2");
handler.setBank(bank3);
Thread t3 = new Thread(handler,"t3");
try {
t2.sleep(10);
t3.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t1.start();
t2.start();
t3.start();
System.out.println("-------------------i am main------");
}
结果:
-------------------i am main------
1498733549403---A--4000
1498733549408----B-----3960
1498733549408---A--3960
1498733549411----B-----3920
1498733549411---A--3920
1498733549414----B-----3881
从结果可以看出:
尽管bank设置有1000,2000,4000.但因为t1,t2,t3并不是new的时候就执行,而是主程序已经结束了才开始的,此时handler中实际的对象为bank3.因此t1,t2,t3中都是4000的值。也就只有bank3一个对象,此对象在里面进行了3次扣款。
public static void main(String[] args) {
BankAccess bank2 = new BankAccess();
BankAccess bank1 = new BankAccess();
bank2.saveMoney(4000);
bank1.saveMoney(2000);
BankTreader handler2 = new BankTreader(bank2);
BankTreader handler1 = new BankTreader(bank1);
Thread t1 = new Thread(handler1,"t1");
Thread t2 = new Thread(handler2,"t2");
t1.start();
t2.start();
System.out.println("-------------------i am main------");
}
结果:
-------------------i am main------
1498734103614---A--2000
1498734103620----B-----1980
1498734103620---A--4000
1498734103623----B-----3960
不同的对象,不同的调用者,但是结果还是线性同步进行的。
一个线程在访问一个对象的同步方法时,另一个线程可以同时访问这个对象的非同步方法。(只要不是同步方法,就不等对象锁,所以不管这个对象是否有其它线程锁定了,在其它线程访问非同步方法都不需要等同步锁的动作 )
package jquery.test.interview;
public class ClassA {
private int iTest;
private final static Object obj = new Object();
public int getiTest() {
System.out.println("---get1--" + System.currentTimeMillis());
//synchronized(obj){
for(int i=0;i<555555555;i++){
int b= i;
}
//}
System.out.println("---get2--" + System.currentTimeMillis());
return iTest;
}
public synchronized void setiTest(int iTest) {
System.out.println("---set1--" + System.currentTimeMillis());
//synchronized(ClassA.class){
for(int i=0;i<iTest;i++){
int b= i;
}
//}
System.out.println("---set2--" + System.currentTimeMillis());
this.iTest = iTest;
}
}
/**
测试类
**/
package jquery.test.interview;
public class finalVarTest {
public static void main(String[] args) {
ClassA a = new ClassA();
Thread t1= new Thread(){
@Override
public void run() {
a.setiTest(555555555);
System.out.println("-----");
}
};
Thread t2= new Thread(){
@Override
public void run() {
System.out.println("---a.getiTest();--" + a.getiTest());
//a.getiTest();
}
};
//try {
t1.start();
try {
t2.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
//t1.join();
//t2.join();
//} catch (InterruptedException e) {
System.out.println("-join----");
// e.printStackTrace();
//}
System.out.println("-main----");
}
}
一个线程在访问一个对象的同步方法时,另一个线程不能同时访问这个同步方法。
死锁:多个线程多个对象互相调用时,要谨防死锁。一般出现在双重同步代码锁中经常出现。如下面代码:两个对象,两个线程,A,B.A对象调用B对象,B对象调用A对象,这个时候就是各自有把锁,却在等待对方释放锁来执行调用的对象。
/**
* 这种classB就容易锁住
*/
public synchronized void setiTest(int iTest) {
System.out.println("---set1--" + System.currentTimeMillis());
for(int i=0;i<iTest;i++){
int b= i;
}
synchronized(classB){
for(int i=0;i<iTest;i++){
int b= i;
}
//}
System.out.println("---set2--" + System.currentTimeMillis());
this.iTest = iTest;
}
public static void main(String[] args) throws InterruptedException {
ClassA c1 = new ClassA();
ClassA c2 = new ClassA();
c1.setClassB(c2);
c2.setClassB(c1);
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
c1.setiTest(50000000);
}
}, "t1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
c2.setiTest(50000000);
}
}, "t2");
t1.start();
t2.start();
System.out.println("-----Main thread----");
}
一个线程在访问一个对象的同步方法时,另一个线程不能同时访问其它同步方法。
如果一个线程在访问一个同步方法,则其他的同步方法此时也不允许其它线程访问,因为方法在调用时锁的是调用者对象,因此其它线程要访问其它方法,而其它方法也是同步的话,就需要解锁,而锁被另一个线程的另一个方法所占用,因此只能等待。
破解的方式:就是将锁加在方法中的代码段上,且对象用一个其它的不变对象,这样锁定的就是本方法,不会影响其它方法对锁的调用。
public synchronized int getiTest() {
System.out.println("---get1--" + System.currentTimeMillis());
//synchronized(obj){
for(int i=0;i<555555555;i++){
int b= i;
}
//}
System.out.println("---get2--" + System.currentTimeMillis());
return iTest;
}
public synchronized void setiTest(int iTest) {
System.out.println("---set1--" + System.currentTimeMillis());
//synchronized(ClassA.class){
for(int i=0;i<iTest;i++){
int b= i;
}
//}
System.out.println("---set2--" + System.currentTimeMillis());
this.iTest = iTest;
}
main方法调用:
public static void main(String[] args) {
ClassA a = new ClassA();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
a.setiTest(600000000);
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
a.getiTest();
}
});
t1.start();
t2.start();
}
结果:
---set1--1498790176386
---set2--1498790176614
---get1--1498790176615
---get2--1498790176827
------------------------------------------------------------------------------
修改get方法为代码段形式
private final static Object obj = new Object();
public int getiTest() {
synchronized(obj){
System.out.println("---get1--" + System.currentTimeMillis());
for(int i=0;i<555555555;i++){
int b= i;
}
System.out.println("---get2--" + System.currentTimeMillis());
}
return iTest;
}
这样get方法的锁对象是obj,而非this。因此不会和set冲突。
结果:
---get1--1498790448547
---set1--1498790448547
---get2--1498790448826
---set2--1498790448844
3.2 volatile 关键字(不稳定的意思)
volatile用于多个线程共享某一数据时,指示读取数据从最新的主存中查找数据,指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
volatile不保证原子操作,很容易读到脏数据。多个线程中共有变量,在不使用同步块时可以考虑用volatile关键字。
因回写的速度较快,因此大量数据的测试,才能看出结果。
private volatile String str = "abc";
public static void main(String[] args) throws InterruptedException, ExecutionException {
finalVarTest fv = new finalVarTest();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
try {
for(int i=0;i<100;i++){
fv.setStr("cde" + i);
Thread.sleep(10);
}
System.out.println("---01-before--1-" + fv.getStr());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
try {
for(int i=0;i<100;i++){
Thread.sleep(10);
System.out.println("---02-before--2-" + fv.getStr());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
System.out.println("I main 1........");
t1.start();
t2.start();
System.out.println("I main 2........");
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
结果:
---当使用volatile时,保证了100条数据t2线程获取到的都是顺序的,从0-99.
---02-before--2-cde0
---02-before--2-cde1
---02-before--2-cde2
---02-before--2-cde3
---02-before--2-cde4
---02-before--2-cde5
---02-before--2-cde6
---02-before--2-cde7
---02-before--2-cde8
---02-before--2-cde9
---02-before--2-cde10
---02-before--2-cde11
---02-before--2-cde12
---02-before--2-cde13
---02-before--2-cde14
---02-before--2-cde15
---02-before--2-cde16
---02-before--2-cde17
---02-before--2-cde18
---02-before--2-cde19
---02-before--2-cde20
---02-before--2-cde21
---02-before--2-cde22
---02-before--2-cde23
---02-before--2-cde24
---02-before--2-cde25
---02-before--2-cde26
---02-before--2-cde27
---02-before--2-cde28
---02-before--2-cde29
---02-before--2-cde30
---02-before--2-cde31
---02-before--2-cde32
---02-before--2-cde33
---02-before--2-cde34
---02-before--2-cde35
---02-before--2-cde36
---02-before--2-cde37
---02-before--2-cde38
---02-before--2-cde39
---02-before--2-cde40
---02-before--2-cde41
---02-before--2-cde42
---02-before--2-cde43
---02-before--2-cde44
---02-before--2-cde45
---02-before--2-cde46
---02-before--2-cde47
---02-before--2-cde48
---02-before--2-cde49
---02-before--2-cde50
---02-before--2-cde51
---02-before--2-cde52
---02-before--2-cde53
---02-before--2-cde54
---02-before--2-cde55
---02-before--2-cde56
---02-before--2-cde57
---02-before--2-cde58
---02-before--2-cde59
---02-before--2-cde60
---02-before--2-cde61
---02-before--2-cde62
---02-before--2-cde63
---02-before--2-cde64
---02-before--2-cde65
---02-before--2-cde66
---02-before--2-cde67
---02-before--2-cde68
---02-before--2-cde69
---02-before--2-cde70
---02-before--2-cde71
---02-before--2-cde72
---02-before--2-cde73
---02-before--2-cde74
---02-before--2-cde75
---02-before--2-cde76
---02-before--2-cde77
---02-before--2-cde78
---02-before--2-cde79
---02-before--2-cde80
---02-before--2-cde81
---02-before--2-cde82
---02-before--2-cde83
---02-before--2-cde84
---02-before--2-cde85
---02-before--2-cde86
---02-before--2-cde87
---02-before--2-cde88
---02-before--2-cde89
---02-before--2-cde90
---02-before--2-cde91
---02-before--2-cde92
---02-before--2-cde93
---02-before--2-cde94
---02-before--2-cde95
---02-before--2-cde96
---02-before--2-cde97
---02-before--2-cde98
---02-before--2-cde99
--当不使用volatile时,不能保证t2取到的数据都是齐的。
---02-before--2-cde1
---02-before--2-cde1
---02-before--2-cde3
---02-before--2-cde3
---02-before--2-cde5
---02-before--2-cde5
---02-before--2-cde6
---02-before--2-cde8
---02-before--2-cde8
---02-before--2-cde9
---02-before--2-cde10
---02-before--2-cde11
---02-before--2-cde12
---02-before--2-cde13
---02-before--2-cde14
---02-before--2-cde15
---02-before--2-cde16
---02-before--2-cde17
---02-before--2-cde18
---02-before--2-cde19
---02-before--2-cde20
---02-before--2-cde21
---02-before--2-cde22
---02-before--2-cde23
---02-before--2-cde24
---02-before--2-cde25
---02-before--2-cde26
---02-before--2-cde27
---02-before--2-cde29
---02-before--2-cde29
---02-before--2-cde30
---02-before--2-cde32
---02-before--2-cde32
---02-before--2-cde33
---02-before--2-cde34
---02-before--2-cde35
---02-before--2-cde36
---02-before--2-cde37
---02-before--2-cde38
---02-before--2-cde40
---02-before--2-cde40
---02-before--2-cde41
---02-before--2-cde42
---02-before--2-cde43
---02-before--2-cde44
---02-before--2-cde45
---02-before--2-cde46
---02-before--2-cde47
---02-before--2-cde48
---02-before--2-cde49
---02-before--2-cde51
---02-before--2-cde51
---02-before--2-cde52
---02-before--2-cde53
---02-before--2-cde54
---02-before--2-cde55
---02-before--2-cde56
---02-before--2-cde57
---02-before--2-cde58
---02-before--2-cde59
---02-before--2-cde60
---02-before--2-cde61
---02-before--2-cde62
---02-before--2-cde63
---02-before--2-cde64
---02-before--2-cde66
---02-before--2-cde67
---02-before--2-cde68
---02-before--2-cde69
---02-before--2-cde70
---02-before--2-cde71
---02-before--2-cde72
---02-before--2-cde73
---02-before--2-cde74
---02-before--2-cde75
---02-before--2-cde76
---02-before--2-cde77
---02-before--2-cde78
---02-before--2-cde79
---02-before--2-cde80
---02-before--2-cde81
---02-before--2-cde82
---02-before--2-cde83
---02-before--2-cde84
---02-before--2-cde85
---02-before--2-cde86
---02-before--2-cde87
---02-before--2-cde88
---02-before--2-cde89
---02-before--2-cde90
---02-before--2-cde91
---02-before--2-cde92
---02-before--2-cde93
---02-before--2-cde94
---02-before--2-cde95
---02-before--2-cde96
---02-before--2-cde97
---02-before--2-cde98
---02-before--2-cde99
4.线程状态

sleep:

4. sleep作用为当前的作用域,而非当前对象。
System.out.println("I main 1........");
t1.start();
t1.sleep(6000); --在主线程中加sleep会作用的主线程上。sleep是在哪加作用到哪地方
System.out.println("I main 2........");
结果:
I main 1........
---01----Thread-0
I main 2........
interrupt:
1. 打断正在Sleep的线程,让其赶紧执行。但是会在sleep中抛出一个innterruptException异常,如果没有sleep,则不会报错。
public static void main(String[] args) throws InterruptedException, ExecutionException {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("--谁把我打断了----");
}
System.out.println("---01----" + Thread.currentThread().getName());
}
});
System.out.println("I main 1........");
t1.start();
//Thread.sleep(100);
t1.interrupt();
System.out.println("I main 2........");
}
结果:
I main 1........
I main 2........
--谁把我打断了----
---01----Thread-0
Interrupt
join:
1. join和sleep一样,都是Thread类的方法。join作用是让“主线程”等待“子线程”结束之后才能继续运行。即当前线程挂起,等子线程运行完后,再继续主线程。
public static void main(String[] args) throws InterruptedException, ExecutionException {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("---01-before--1-");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("I main 1........");
t1.start();
t1.join();
System.out.println("I main 2........");
}
--------------------------------------------------------------
结果:
I main 1........
---01-before--1-abc
I main 2........
join(long millis); join(long millis, int nanos); --设置时间
yeald(屈服): Thread.yield()
- Yield是一个静态的原生(native)方法
- Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
- Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态。 //不能保证立即交付,因cpu中有可能已经存在正在执行的。
- 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态
public static void main(String[] args) throws InterruptedException, ExecutionException {
Thread producer = new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<15;i++){
System.out.println("---producer--1-");
}
}
});
Thread customer = new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<15;i++){
System.out.println("---customer--2-");
}
}
});
System.out.println("I main 1........");
//producer.setPriority(Thread.MIN_PRIORITY);
//customer.setPriority(Thread.MAX_PRIORITY);
producer.start();
customer.start();
System.out.println("I main 2........");
}
-------------------------------------------------------------
不带优先级时: 打印中customer 和productor交叉出现。
带优先级时,先打印customer后打印productor
yield后customer的优先权就转出来,这时候productor就有了执行的机会。但customer比productor有高优先级,因此还是会有customer在中间执行。
public static void main(String[] args) throws InterruptedException, ExecutionException {
Thread producer = new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<15;i++){
System.out.println("---producer--1-");
}
}
});
Thread customer = new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<15;i++){
if(i>8){ //当执行8个时让渡
Thread.yield();
}
System.out.println("---customer--2-");
}
}
});
System.out.println("I main 1........");
producer.setPriority(Thread.MIN_PRIORITY);
customer.setPriority(Thread.MAX_PRIORITY);
producer.start();
customer.start();
System.out.println("I main 2........");
}
---------------------------------------------------------------------------
结果:
---customer--2-0
---producer--1-0
---customer--2-1
---producer--1-1
---customer--2-2
---producer--1-2
---customer--2-3
---customer--2-4
---customer--2-5
---customer--2-6
---customer--2-7
---customer--2-8
---customer--2-9
---producer--1-3
---producer--1-4
---producer--1-5
---producer--1-6
---producer--1-7
---producer--1-8
---producer--1-9
---producer--1-10
---producer--1-11
---producer--1-12
---producer--1-13
---producer--1-14
---customer--2-10
---customer--2-11
---customer--2-12
---customer--2-13
---customer--2-14
wait和notify,notifyAll
wait() 与 notify/notifyAll() 是Object类的方法,在执行两个方法时,要先获得锁。因此他们大多都是写在synchronized中。
wait和notify的执行顺序不能错,不然先notify后wait则程序等待。
在main方法中写 customer.wait();报错 java.lang.IllegalMonitorStateException,因为wait和notify要拿到锁才可以,此时因没有拿到锁所以报错。
且wait和notify必须作用于同一个对象。要不然不起作用。
package jquery.test.interview;
public class ClassA {
private int iTest;
private final static Object obj = new Object();
private ClassA classB;
public ClassA getClassB() {
return classB;
}
public void setClassB(ClassA classB) {
this.classB = classB;
}
public int getiTest() throws InterruptedException {
System.out.println("---get1--" + System.currentTimeMillis());
synchronized(obj){
obj.wait();
for(int i=0;i<555555555;i++){
int b= i;
}
System.out.println("---get2--" + System.currentTimeMillis());
}
return iTest;
}
public synchronized void setiTest(int iTest) {
System.out.println("---set1--" + System.currentTimeMillis());
for(int i=0;i<iTest;i++){
int b= i;
}
System.out.println("---set2--" + System.currentTimeMillis());
this.iTest = iTest;
}
}
ClassA
ClassA ca = new ClassA();
Thread t1= new Thread(){
@Override
public void run() {
ca.setiTest(50000);
};
};
Thread t2= new Thread(){
@Override
public void run() {
try {
ca.getiTest();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
};
t1.start();
t2.start();
------------------------------------------------------------------------------
Test Main
结果: t2执行wait后,释放了锁,进入等待状态,但是没有唤醒的线程来重新唤醒,因此程序处于一直运行等待中,而未结束,除非使用interrupt,或者stop强行停止。
多用于生产者和消费者模式:
package jquery.test.interview;
import java.util.Vector;
public class Productor<T> implements Runnable {
private volatile Vector<T> v;
public Productor(Vector<T> v){
this.v = v;
}
/**
* 生产产品
* @return
*/
public void createProduct(){
synchronized(v){
while(true){
System.out.println("库存产品数....1.." + v.size());
if(v.isEmpty() || v.size() < 3){
System.out.println("库存紧张,开始生产......");
v.add((T)"product1.....");
v.add((T)"product2.....");
v.add((T)"product3.....");
v.add((T)"product4.....");
v.add((T)"product5.....");
}
//开始等待
try {
System.out.println("库存产品数...2..." + v.size());
v.notifyAll();
v.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void run() {
createProduct();
}
}
Productor
package jquery.test.interview;
import java.util.Vector;
public class Customer<T> implements Runnable {
private volatile Vector<T> vector;
public Customer(Vector<T> vector){
this.vector = vector;
}
public void getProduct(){
synchronized(vector){
while(true){
if(null == vector || vector.isEmpty()){
try {
System.out.println("--没有产品等待中.....");
vector.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("--获取产品使用....." + vector.get(0));
vector.remove(0);
vector.notify();
}
}
}
}
@Override
public void run() {
getProduct();
}
}
Customer
测试及结果:
public static void main(String[] args) throws InterruptedException, ExecutionException {
Vector<String> product = new Vector<String>();
ExecutorService pool = Executors.newCachedThreadPool();
Productor<String> proc = new Productor<String>(product);
pool.submit(proc);
for(int i =0;i<5;i++){
Customer<String> cus = new Customer<String>(product);
pool.submit(cus);
}
}
-------------------------------------------------------------------------------
库存产品数....1..0
库存紧张,开始生产......
库存产品数...2...5
--获取产品使用.....product1.....
--获取产品使用.....product2.....
--获取产品使用.....product3.....
--获取产品使用.....product4.....
--获取产品使用.....product5.....
--没有产品等待中.....
库存产品数....1..0
库存紧张,开始生产......
库存产品数...2...5
--获取产品使用.....product1.....
--获取产品使用.....product2.....
--获取产品使用.....product3.....
--获取产品使用.....product4.....
--获取产品使用.....product5.....
--没有产品等待中.....
--没有产品等待中.....
--没有产品等待中.....
--没有产品等待中.....
库存产品数....1..0
库存紧张,开始生产......
库存产品数...2...5
--获取产品使用.....product1.....
--获取产品使用.....product2.....
--获取产品使用.....product3.....
--获取产品使用.....product4.....
--获取产品使用.....product5.....
--没有产品等待中.....
库存产品数....1..0
库存紧张,开始生产......
库存产品数...2...5
--获取产品使用.....product1.....
--获取产品使用.....product2.....
--获取产品使用.....product3.....
--获取产品使用.....product4.....
--获取产品使用.....product5.....
--没有产品等待中.....
--没有产品等待中.....
--没有产品等待中.....
--没有产品等待中.....
--没有产品等待中.....
库存产品数....1..0
库存紧张,开始生产......
库存产品数...2...5
--获取产品使用.....product1.....
--获取产品使用.....product2.....
--获取产品使用.....product3.....
--获取产品使用.....product4.....
--获取产品使用.....product5.....
--没有产品等待中.....
--没有产品等待中.....
--没有产品等待中.....
测试
新建线程:
1.new Thread
public static void main(String[] args) {
ClassA a = new ClassA();
Thread t1 = new Thread(){
@Override
public void run() {
//TODO
System.out.println("------Thread t1----");
a.getiTest();
}
};
t1.start();
System.out.println("-----Main thread----");
}
结果:------------------------------------------------------------
优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。也可用 MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级
2. new Runnable或者实现runnable接口
public static void main(String[] args) {
ClassA a = new ClassA();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
//TODO
System.out.println("------Runnable t1----");
a.getiTest();
}
});
t1.start();
System.out.println("-----Main thread----");
}
实现接口的方式:
/**
*实现Runnable接口
*/
public class BankTreader implements Runnable {
@Override
public void run() {
handlerBank();
}
-----------------------------------------------
public static void main(String[] args) {
BankTreader b1 = new BankTreader();
Thread t1 = new Thread(b1, "t1");
t1.start();
System.out.println("-----Main thread----");
}
实现接口的方式会束缚: 多线程调用代码被固定死。 比如在BankThreader中 只能调用 handlerBank();而不能调用其它方法。而第一种就不牵扯这个问题,在需要的时候构造Runnable,传入需要调用方法。
3. 线程池
有这样1个场景,现在有100个数据需要处理,如果我们为其开辟new 100个线程,一次开销太大,同时cpu也不可能同时消化的了,从资源占有和性能上反而是种浪费。因此这就需要合理的开辟线程,假设我们开辟10个线程,这样每个线程去处理10笔数,这种就解决了性能和资源占有的问题。但是线程的执行不是线性的,不是A线程执行1个后,B线程执行1个,然后。。。。。,而是无序的有可能C线程都执行了2笔数据但A线程1笔都没有抢到。因此,为了合理的调度10个线程,使其尽量都满负荷的完成任务。就引入了线程池管理。
ThreadPoolExecutor:
详细的工作原理参考:(深入理解Java之线程池)http://www.cnblogs.com/dolphin0520/p/3932921.html
ThreadPoolExecutor 主要方法说明:
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()和shutdownNow()是用来关闭线程池的。
线程池使用:
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种是动态线程数的线程池,适合处理量不是特别大的线程数,比如100个数据就会造成在开始运行时开辟过多线程,但5个或10个时就负载小。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。这种和第一种相反,适合大量数据长时计算,在保证运算速率时,又不挤兑其它程序的运行。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。这种是定时运行,延迟运行程序用,定时任务这种就比较适合。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。这是线性运行,一次保证只有一个运行,且必须依照顺序进行,不会出现无序状况。
创建连接池,通过utils下的Executors创建连接池,java监控(C:\Program Files (x86)\Java\jdk1.8.0_121\bin\jconsole.exe)执行结果。
Thread.sleep(100);//线程睡眠
Thread.currentThread().getName(); //获取当前运行thread的名字
线程池用完要关闭,es.shutdown();

public static void main(String[] args) throws InterruptedException {
Thread.sleep(2000); //去监控器找到这个java,启用监控
Executor ex = Executors.newFixedThreadPool(10);
for(int i=0; i < 100; i++){
final int fi = i;
ex.execute(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(100); //延缓,使得线程不能及时结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("####" + Thread.currentThread().getName() + "------------" + fi);
}
});
}
}
newFixedThreadPool

// 延迟执行
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService ex = Executors.newScheduledThreadPool(8);
for(int i=0; i < 10; i++){
final int fi = i;
ex.schedule(new Runnable(){ //延迟执行
@Override
public void run() {
System.out.println("####" + System.currentTimeMillis() + "------------" + fi);
}
}, 2, TimeUnit.SECONDS); //延迟2秒执行
}
}
// 定时执行
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService ex = Executors.newScheduledThreadPool(8);
for(int i=0; i < 2; i++){
final int fi = i;
ex.scheduleAtFixedRate(new Runnable(){ //定时执行
@Override
public void run() {
System.out.println("####" + System.currentTimeMillis() + "------------" + fi);
}
}, 0, 2, TimeUnit.SECONDS); //每次隔2秒执行
}
}
newScheduledThreadPool
4. Callable<V> 接口,Future<V> 接口,FutureTask实现类
因实现Runnable和继承Thread方式创建的线程都是没有回调函数的,也就是不知道什么时候结束的,哪一个结束了等。没法满足对线程结束时的管理操作。
Java SE 5.0引入的Callable和Future,可以构建带回调函数的线程接口。
Callable<V> 带有回调方法的接口V call()。Runnable接口void run()方法。
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Future<V>接口是用来获取异步计算结果的,可判断是否线程执行结束,获取结果,取消等操作 。
因此,这两个要在一起连用才可以获取多线程的结果。
cancel方法中的参数params ,如果为true则试图中断当前的操作,返回true,false则等待当前的操作完成返回false.
get()可阻塞当前主线程,等待子线程完成后进行。
isDone可判断子线程是否已经结束,可用 while(!f.isDone){system.out.println("还没完还没完")}等待并检查是否完成。
FutureTask实现Runnable和Future接口的实现类,但没有实现Callable接口,callable要自己创建实现类,通过FutureTask构造器传入使用。
public interface RunnableFuture<V> extends Runnable, Future<V> {
public class FutureTask<V> implements RunnableFuture<V> {
FutureTask(Callable<V> callable)
FutureTask(Runnable runnable, V result)
public static void main(String[] args) throws InterruptedException, ExecutionException {
String result = "1234";
FutureTask<String> ft0 = new FutureTask<String>(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------runnable----");
}}, result);
FutureTask<String> ft = new FutureTask<String>(new Callable<String>(){
@Override
public String call() throws Exception {
System.out.println("-------call----");
return "hello:---call";
}
});
Thread t1 = new Thread(ft);
t1.start();
Thread t0 = new Thread(ft0);
t0.start();
if(ft.get() != null){ //主线程在此处会等待其它线程完成,获取结果
System.out.println(ft.get());
}
while(!ft0.isDone()){
System.out.println("----还没结束----");
}
System.out.println(ft0.get());
}
FutureTask
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(5);
List<Future<String>> list = new ArrayList<Future<String>>();
for(int i=0;i<10;i++){
final int fi = i;
Future<String> fu = es.submit(new Callable<String>(){
@Override
public String call() throws Exception {
System.out.println("---ok call--" + fi);
return "I'm callable----" + fi;
}
});
list.add(fu);
}
es.shutdown();
// 结果输出
for(Future<String> fu : list){
System.out.println(fu.get());
}
}
Future和Callable
最后用FutureTask来实现连接池调用
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(5);
List<FutureTask<String>> list = new ArrayList<FutureTask<String>>();
for(int i=0;i<10;i++){
final int fi = i;
FutureTask<String> ft = new FutureTask<String>(new Callable<String>(){
@Override
public String call() throws Exception {
System.out.println("---ok call--" + fi);
return "I'm callable----" + fi;
}
});
es.submit(ft);
list.add(ft);
}
es.shutdown();
// 结果输出
for(Future<String> fu : list){
System.out.println(fu.get());
}
}
参考:
Java中的多线程你只要看这一篇就够了http://www.cnblogs.com/wxd0108/p/5479442.html
Java多线程学习(吐血超详细总结)http://www.mamicode.com/info-detail-517008.html
(深入理解Java之线程池)http://www.cnblogs.com/dolphin0520/p/3932921.html
(Java四种线程池的使用)http://cuisuqiang.iteye.com/blog/2019372
多线程--Java的更多相关文章
- 多线程Java Socket编程示例
package org.merit.test.socket; import java.io.BufferedReader; import java.io.IOException; import jav ...
- 多线程java的concurrent用法详解(转载)
我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便.而当针对高质量Java ...
- java多线程-Java中的Copy-On-Write容器
Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改, ...
- 多线程Java Socket编程示例(转)
这篇做为学习孙卫琴<<Java网络编程精解>>的学习笔记吧.其中采用Java 5的ExecutorService来进行线程池的方式实现多线程,模拟客户端多用户向同一服务器端发送 ...
- java核心-多线程-Java多线程编程涉及到包、类
Java有关多线程编程设计的类主要涉及两个包java.lang和java.util.concurrent两个包 java.lang包,主要是线程基础类 <1>Thread <2> ...
- 多线程Java Socket编程
采用Java 5的ExecutorService来进行线程池的方式实现多线程,模拟客户端多用户向同一服务器端发送请求. 1.服务端 package localSocket; import java.i ...
- Java多线程-Java多线程概述
第一章 Java多线程概述 线程的启动 线程的暂停 线程的优先级 线程安全相关问题 1.1 进程与线程 进程:可以将运行在内存中的程序(如exe文件)理解为进程,进程是受操作系统管理的基本的运行单元. ...
- 2.1多线程(java学习笔记) java中多线程的实现(附静态代理模式)
一.多线程 首先我们要清楚程序.进程.线程的关系. 首先进程从属于程序,线程从属于进程. 程序指计算机执行操作或任务的指令集合,是一个静态的概念. 但我们实际运行程序时,并发程序因为相互制约,具有“执 ...
- 多线程-java并发编程实战笔记
线程安全性 编写线程安全的代码实质上就是管理对状态的访问,而且通常都是共享的,可变的状态. 一个对象的状态就是他的数据,存储在状态变量中,比如实例域或静态域.所谓共享是指一个对象可以被多个线程访问:所 ...
随机推荐
- 【python下使用OpenCV实现计算机视觉读书笔记3】读写视频文件
代码例如以下: import cv2 videoCapture = cv2.VideoCapture('car.avi') fps = videoCapture.get(cv2.cv.CV_CAP_P ...
- 哈勃(Hubble)望远镜的新发现
请看下图: itok=FcRLe7t_" name="图形1" alt="" border="0" height ...
- BatSendMail
@echo off echo ==================================echo == Compress Files And Send Mail ==echo ======= ...
- HTTP报头:通用报头,请求报头,响应报头和实体报头
缓存控制优先级从高到低分别是Pragma -> Cache-Control -> Expires 报头 每一个报头都是由 [名称 + ":" + 空格 + 值 + ] ...
- SEO之H1,H2,H3,H4....STRONG使用方法
作为一个SEO从业人员,我们不仅仅是要懂得如何通过网站内容和外链等SEO手段,其实一个优秀的SEOER在从事一个SEO案例时,首先着手的是如何从网站程序本身来打好网站SEO基础. 在平时和很多朋友的交 ...
- Django 博客项目02 Form验证+ 上传头像(预览)+Ajax用户注册
头像预览 $("#avatar_file").change(function(){ // 获取上传的文件对象 var file=$(this)[0].files[0]; // 读取 ...
- ssl证书(https) iis 配置安装
因客户给的 cer的文件 导入提示 失败,所以用 了 客户给的 crt的格式的证书. 安装证书操作如下:iis>>服务器证书>>右侧菜单-完成证书申请>>选择 本文 ...
- Android Studio 默认keystore 以及自定义keystore
我们使用Android Studio 运行或测试我们的app 它使用一个默认的debug.keystore进行签名. 这个默认签名(keystore)是不需要密码的,它的默认位置在 $HOME/.a ...
- java DateUtils
package demoone; import java.sql.Timestamp; import java.text.ParseException; import java.text.Simple ...
- 杂项:Nuget
ylbtech-杂项:Nuget Nuget是一个.NET平台下的开源的项目,它是Visual Studio的扩展.在使用Visual Studio开发基于.NET Framework的应用时,Nug ...