一、java线程的创建与启动
1.定义线程
2.实例化线程
3.启动线程
在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。
举例说明
实现Runnable接口的例子:class DoTest implements Runnable {
private String name;
public DoTest(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
for (long k = 0; k < 10000000; k++);
System.out.println(name + ": " + i);
}
}
}
public class TestRunnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
DoTest dt1=new DoTest("线程1");
DoTest dt2=new DoTest("线程2");
Thread t1=new Thread(dt1);
Thread t2=new Thread(dt2);
t1.start();
t2.start();
}
}
继承Thread类的例子
public class TestThread extends Thread {
private String name;
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1=new TestThread("线程1");
Thread t2=new TestThread("线程2");
t1.start();
t2.start();
}
public TestThread(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
for (long k = 0; k < 10000000; k++);
System.out.println(name + ": " + i);
}
}
}
二:线程常用的方法
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
方法所用的位置:为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠。
1、线程睡眠是帮助所有线程获得运行机会的最好方法。
2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
3、sleep()是静态方法,只能控制当前正在运行的线程。
3.线程的优先级设定(非静态方法)public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1=new TestThread("线程1");
Thread t2=new TestThread("线程2");
t1.setPriority(4);
t1.start();
t2.start();
}
线程默认优先级是5,范围是1-10,Thread类中有三个常量,定义线程优先级范围:
static int MAX_PRIORITY
线程可以具有的最高优先级。
static int MIN_PRIORITY
线程可以具有的最低优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。
Thread.yield()方法:暂停当前正在执行的线程对象,并执行其他线程
public class TestThread implements Runnable{
public static int a = 0;
public void run() {
for (int k = 0; k < 5; k++) {
a = a + 1;
}
}
public static void main(String[] args) throws Exception {
Runnable r = new TestThread();
Thread t = new Thread(r);
t.start();
System.out.println(a);
}
}
这个结果是不确定的,根据不同的机器。原因是这个程序存在两个线程,一个是主线程main,一个是线程t。当主线程执行完t.start()的时候,则会继续往下执行。而这时的线程t也许还在分配资源之类的工作,而a已经输出啦!所以结果不是5.
public class TestThread implements Runnable{
public static int a = 0;
public void run() {
for (int k = 0; k < 5; k++) {
a = a + 1;
}
}
public static void main(String[] args) throws Exception {
Runnable r = new TestThread();
Thread t = new Thread(r);
t.start();
t.join();//1.加入join()方法,表示在线程t未结束之前,线程main()不能执行
t.join(1000);//2.表示线程main得等待线程t执行1000ms之后,线程main才能继续执行,否则一直等待
System.out.println(a);
}
}
三 线程的同步与锁
1.volatile关键词public class TestVolatile {
public volatile static int count = 0;
public static void inc() {
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
}
public static void main(String[] args) {
//同时启动1000个线程,去进行i++计算,看看实际结果
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
TestVolatile.inc();
}
}).start();
}
//这里每次运行的值都有可能不同,可能不为1000
System.out.println("运行结果:Counter.count=" + TestVolatile.count);
}
}
注意:其结果不是1000。原因是Volatile并不能阻止线程之间发生并发性。
2.synchronized关键字
方法或代码块的互斥性来完成实际上的一个原子操作。(方法或代码块在被一个线程调用时,其他线程处于等待状态)
所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。(锁是针对的对象,而不是方法),所以这个synchronized分为两种:
1)在某个对象实例内(针对的是实例方法和synchronized(类名.class)这样的代码块)
2)在某个类内(针对的是非实例方法和synchronized(this)这样的代码块)
各位看官,不要着急,我们来具体解释一下(以前我总认为是针对的方法,但是遇到多线程并发的时候,这个理解解释不了一些事情)
锁定对象实例:class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
public void deposit(float amt) {
float tmp = amount;
tmp += amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public void withdraw(float amt) {
float tmp = amount;
tmp -= amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public float getBalance() {
return amount;
}
}
public class AccountTest{
private static int NUM_OF_THREAD = 1000;
static Thread[] threads = new Thread[NUM_OF_THREAD];
public static void main(String[] args){
final Account acc = new Account("John", 1000.0f);
for (int i = 0; i< NUM_OF_THREAD; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
acc.deposit(100.0f);
acc.withdraw(100.0f);
}
});
threads[i].start();
}
for (int i=0; i<NUM_OF_THREAD; i++){
try {
threads[i].join(); //等待所有线程运行结束
} catch (InterruptedException e) {
// ignore
}
}
System.out.println("Finally, John's balance is:" + acc.getBalance());
}
}
这时候的结果是
class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
public synchronized void deposit(float amt) {
float tmp = amount;
tmp += amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public synchronized void withdraw(float amt) {
float tmp = amount;
tmp -= amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public float getBalance() {
return amount;
}
}
public class AccountTest{
private static int NUM_OF_THREAD = 1000;
static Thread[] threads = new Thread[NUM_OF_THREAD];
public static void main(String[] args){
final Account acc = new Account("John", 1000.0f);
for (int i = 0; i< NUM_OF_THREAD; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
acc.deposit(100.0f);
acc.withdraw(100.0f);
}
});
threads[i].start();
}
for (int i=0; i<NUM_OF_THREAD; i++){
try {
threads[i].join(); //等待所有线程运行结束
} catch (InterruptedException e) {
// ignore
}
}
System.out.println("Finally, John's balance is:" + acc.getBalance());
}
}
Finally, John's balance is:1000.0
class Account {
String name;
static float amount;
public Account(String name) {
this.name = name;
}
public synchronized void deposit(float amt) {
float tmp = amount;
tmp += amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public synchronized void withdraw(float amt) {
float tmp = amount;
tmp -= amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public float getBalance() {
return amount;
}
}
public class AccountTest{
private static int NUM_OF_THREAD = 1000;
static Thread[] threads = new Thread[NUM_OF_THREAD];
static int flag=0;
public static void main(String[] args){
Account.amount=1000;
final Account acc1 = new Account("John");
final Account acc2 = new Account("John2");
for (int i = 0; i< NUM_OF_THREAD; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
if(flag==0)
{
acc1.deposit(100.0f);
acc1.withdraw(100.0f);
flag=1;
}
else{
acc2.deposit(100.0f);
acc2.withdraw(100.0f);
flag=0;
}
}
});
threads[i].start();
}
for (int i=0; i<NUM_OF_THREAD; i++){
try {
threads[i].join(); //等待所有线程运行结束
} catch (InterruptedException e) {
// ignore
}
}
System.out.println("Finally, John's balance is:" + acc1.getBalance());
}
}
其结果为:
Finally, John's balance is:14100.0。
account属性虽然为静态变量,属于类的范畴,但是两个方法仍然是对象实例的范畴。acc1对象的线程1正在访问deposit方法,则acc1对象的其它线程是不可能再访问方法的,都会因为synchronized关键字而阻塞。但是这跟acc2对象的线程没有任何关系,acc2的线程是可以访问deposit方法的,所以就造成了上述的并发现象。说明每个对象是一把锁,其只关心自己的锁。所以synchronized修饰非静态方法是属于对象实例锁。
class Account {
String name;
static float amount;
public Account(String name) {
this.name = name;
}
public synchronized static void deposit(float amt) {
float tmp = amount;
tmp += amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public synchronized static void withdraw(float amt) {
float tmp = amount;
tmp -= amt;
try {
Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public float getBalance() {
return amount;
}
}
public class AccountTest{
private static int NUM_OF_THREAD = 1000;
static Thread[] threads = new Thread[NUM_OF_THREAD];
static int flag=0;
public static void main(String[] args){
Account.amount=1000;
final Account acc1 = new Account("John");
final Account acc2 = new Account("John2");
for (int i = 0; i< NUM_OF_THREAD; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
if(flag==0)
{
acc1.deposit(100.0f);
acc1.withdraw(100.0f);
flag=1;
}
else{
acc2.deposit(100.0f);
acc2.withdraw(100.0f);
flag=0;
}
}
});
threads[i].start();
}
for (int i=0; i<NUM_OF_THREAD; i++){
try {
threads[i].join(); //等待所有线程运行结束
} catch (InterruptedException e) {
// ignore
}
}
System.out.println("Finally, John's balance is:" + acc1.getBalance());
}
}
其结果为:
上述都是针对方法来说的,我们经常见到的是对代码块上锁,synchronized(this或者类){}。其实也属于对象实例或者类。总结如下:
(1)当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
(2)然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
(3)尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
(4)第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
(5)以上规则对其它对象锁同样适用.
四、线程之间的相互唤醒(本节完全参考http://blog.csdn.net/zyplus/article/details/6672775这个博客,写的比较不错)
单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:
public class MyThreadPrinter2 implements Runnable {
private String name;
private Object prev;
private Object self;
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
new Thread(pa).start();
new Thread(pb).start();
new Thread(pc).start(); }
}
先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。具体来说就是,在main主线程启动ThreadA后,需要在ThreadA执行完,在prev.wait()等待时,再切回线程启动ThreadB,ThreadB执行完,在prev.wait()等待时,再切回主线程,启动ThreadC,只有JVM按照这个线程运行顺序执行,才能保证输出的结果是正确的。而这依赖于JVM的具体实现。考虑一种情况,如下:如果主线程在启动A后,执行A,过程中又切回主线程,启动了ThreadB,ThreadC,之后,由于A线程尚未释放self.notify,也就是B需要在synchronized(prev)处等待,而这时C却调用synchronized(prev)获取了对b的对象锁。这样,在A调用完后,同时ThreadB获取了prev也就是a的对象锁,ThreadC的执行条件就已经满足了,会打印C,之后释放c,及b的对象锁,这时ThreadB具备了运行条件,会打印B,也就是循环变成了ACBACB了。
new Thread(pa).start();
Thread.sleep(100);
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();