这块一直是薄弱的地方,只有模糊的概念,没有实战的经验~借此学习学习~
实现线程的两种方式:
1.继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中;
2.实现java.lang.Runnable接口,实现它的run()方法,将线程的执行主体放入其中。
线程类有一个Runnable类型的target属性,它是线程启动后要执行的run()方法所属主体,如果采用的是继承Thread类的方式,那么这个target就是线程对象自身;如果采用的是实现Runnable接口的方式,那么这个target就是实现了Runnable接口的类的实例。
接下来说同步
同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,同步方法和同步块,都要用到synchronized关键字。
*同步方法
给一个方法加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法。
先看一段代码:
public class ThreadTest1 extends Thread {
private int threadNo;
public ThreadTest1(int threadNo) {
this.threadNo = threadNo;
}
public synchronized void run() {
for(int i=0;i<100;i++){
System.out.println("线程"+threadNo+":"+i);
}
}
public static void main(String[] args)throws Exception {
for(int j=1;j<10;j++){
new ThreadTest1(j).start();
Thread.sleep(1);
}
}
}
这个程序其实就是让10个线程在控制台上数数,从0~99。理想情况下,我们希望看到一个线程数完,然后才是另一个线程开始数数。这是当你执行了这个程序之后就会发现,这些线程并没有按照我们的期望执行,是没有规律的抢着报数。
也许你会说:怎么会这样?!不是已经在run()方法前加synchronized关键字了么,按理来说,这些线程应该可以一个接一个的执行run()方法啊?!
对于一个非静态成员方法加synchronized关键字,这实际上是以这个成员方法所在的对象本身作为对象锁。在这个例子中,就是以ThreadTest1类的一个具体对象,也就是该线程自身作为对象锁的。一共十个线程,每个线程持有自己线程对象的那个对象锁,这必然不能产生同步的效果。
再看这段代码:
public class ThreadTest2 extends Thread {
private int threadNo;
public ThreadTest2(int threadNo) {
this.threadNo = threadNo;
}
public void run() {
abc(threadNo);
}
public static synchronized void abc(int threadNo) {
for(int i=0;i<100;i++){
System.out.println("线程"+threadNo+":"+i);
}
}
public static void main(String[] args)throws Exception {
for(int j=1;j<10;j++){
new ThreadTest2(j).start();
Thread.sleep(1);
}
}
}
这个代码是run()方法调用一个静态的同步方法abc(),运行该程序你会发现一个线程数完0~99另一个线程才开始。
对于同步静态方法,对象锁就是该方法所在类的Class实例,在JVM中,所有被加载的类都有唯一的类对象,具体到本例,就是唯一的ThreadTest2.class对象。不管我们创建了该类的多少个实例,但是它的类实例就一个。
一但一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象。
再来看一段代码:
public class ThreadTest3 extends Thread {
private int threadNo;
private String lock;
public ThreadTest3(int threadNo,String lock) {
this.threadNo = threadNo;
this.lock = lock;
}
public void run() {
synchronized(lock){
for(int i=0;i<100;i++){
System.out.println("线程"+threadNo+":"+i);
}
}
}
public static void main(String[]args)throws Exception{
String lock = new String("lock");
for(int j=1;j<10;j++){
new ThreadTest3(j,lock).start();
Thread.sleep(1);
}
}
}
我们注意到,该程序通过在main()方法启动10个线程之前,创建了一个String类型的对象。并通过ThreadTest3的构造函数,将这个对象赋值给每一个ThreadTest3线程对象中的私有变量lock,这些线程的lock变量实际上指向的都是堆内存中的同一区域,即存放main函数中的lock变量的区域。run()方法里的synchronized块,这个同步块的对象锁,就是main方法中创建的那个String对象。指向的是同一个String类型的对象,对象锁是共享且唯一的!