线程同步之synchronized关键字
今天试了试线程同步,运行代码发现和synchronized关键字介绍的不太一样。
起初代码是这样:
public class Test{
public static void main(String args[]){
Thread cat = new Thread(new sayhello());
Thread dog = new Thread(new sayhello());
dog.setName("dog");
cat.setName("cat");
cat.start();
dog.start();
}
}
class sayhello implements Runnable{
@Override
public synchronized void run() {
String name = Thread.currentThread().getName();
for(int i = 0; i < 5; i++){
System.out.printf("%s:%d\n",name,i);
}
}
}
运行结果:
从结果上看到这里和synchronized的线程同步介绍一样,当线程cat执行run(),dog无法执行run(),等cat执行完成run()后才能让dog执行。但是可能是输出5次的时间短于jvm分配给cat执行的时间,我们把循环次数加到1000看看。
循环次数加到1000后明显看出线程没有同步,换句话说,cat线程执行run()方法时没有把run()方法锁起来不让别的线程使用。
另外,我还使用过Thread.sleep()方法,结果是一样的,线程不是同步的。
点击查看代码
public class Test{
public static void main(String args[]){
Thread cat = new Thread(new sayhello());
Thread dog = new Thread(new sayhello());
dog.setName("dog");
cat.setName("cat");
cat.start();
dog.start();
}
}
class sayhello implements Runnable{
@Override
public synchronized void run() {
String name = Thread.currentThread().getName();
for(int i = 0; i < 1000; i++){
try {
Thread.sleep(1000);
System.out.printf("%s:%d\n",name,i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
sleep()方法会让线程休眠,说起休眠不得不提到线程的生命周期了。线程的生命周期分为新建、启动、中断、死亡,使用sleep()方法后线程处在终端状态。如果不是synchronized修饰的方法、静态代码块,线程在执行了sleep()方法后JVM会将资源分配给其他线程,但是使用synchronized修饰后一定要等待该线程执行完synchronized修饰的方法、静态代码块后才能让下一个静态代码块执行。
为什么这里的synchronized没有生效呢?原因是在这里
Thread cat = new Thread(new sayhello());
Thread dog = new Thread(new sayhello());
这里新建了两个sayhello类的对象,这两个对象是不共享run()这个方法的。
如果要让run()方法是线程同步的
第一种方法是给run()方法加上static关键字,这样run()方法就类方法了,两个对象共享这个类方法。但是这里有一个问题,run()是重写的方法,加了static方法后就不是重写的方法了。所以这个方案排除。
第二种方法是使用同一个sayhello对象新建两个不同线程,线程就共享run()方法了。
下面我们来看看代码:
public class Test{
public static void main(String args[]){
sayhello say = new sayhello();
Thread cat = new Thread(say);
Thread dog = new Thread(say);
dog.setName("dog");
cat.setName("cat");
cat.start();
dog.start();
}
}
class sayhello implements Runnable{
@Override
public synchronized void run() {
String name = Thread.currentThread().getName();
for(int i = 0; i < 1000; i++){
System.out.printf("%s:%d\n",name,i);
}
}
}
运行结果:
既然synchronized是对线程间共享的方法、静态代码块,决定共享在于创建线程的类的对象对于这些资源是不是共享的。那么我们的代码还可以这么写:
public class Test{
public static void main(String args[]){
Thread cat = new Thread(new sayhello());
Thread dog = new Thread(new sayhello());
dog.setName("dog");
cat.setName("cat");
cat.start();
dog.start();
}
}
class sayhello implements Runnable{
public synchronized static void shushu(){
String name = Thread.currentThread().getName();
for(int i = 0; i < 1000; i++){
System.out.printf("%s:%d\n",name,i);
}
}
@Override
public synchronized void run() {
shushu();
}
}
运行结果:
由于静态方法是所有对象共享的,所以创建的两个对象共享这个静态方法。
我们可以发现,当线程之间有共享的资源被synchronized修饰时,一个线程在使用这些资源直到执行完这些资源中的所有代码后,其他线程才能使用这些资源。
看到这里,我不禁想什么是线程?什么是线程对象?如何创建线程对象?
创建一个线程对象,可以继承Thread类然后直接新建一个对象,也可以使用Thread thread = new Thread(new XXX)来创建;或者实现Runnable方法后,使用Thread thread = new Thread(new XXX);方法创建线程。这里为什么都使用到了继承Thread的类或者实现Runnable的类创建的对象?
当一个线程启动后,会像主线程中一样顺序执行该线程中的代码。至于什么时候执行,就要看JVM如何分配。想象一下,我们开了三个线程,然后JVM就在指挥这些线程,线程1先运行1秒,然后线程2运行2秒,然后线程3……
又想到,当一个对象被创建的时候,也就重新开辟了成员方法和成员变量的入口。
除了主线程,其他线程都是执行对象类中的run()方法,run()方法也是方法,而且是一个成员方法。能调用它的,只有对应类的对象。所以这才有了需要使用对应类对象类创建线程。可以这么理解吗?线程只是拥有了在JVM哪里排队的资格,等排上队了,要最什么,就是对应类的run()方法了。这个run()方法,可以属于同一个对象的,也可以是不同对象的。同一个对象的使用synchronized就能控制run()方法在不同线程之间的同步了。在该类中的其他方法也可以使用synchronized修饰,然后在run()方法中调用即可。
稍微提取一下synchronized的关键用法:
synchronized用来控制多个线程执行同一个对象的同一个方法,只能有一个线程正在执行synchronized修饰的方法、代码块。
使用了synchronized关键字后,JVM是不是一直要把CPU的使用权交给正在执行synchronized方法、代码块的线程直到synchronized方法、代码块执行完后呢?答案是不是的。JVM一样会把CPU的使用权交给其他线程,其他线程可以执行非synchronized方法、代码块。执行到synchronized方法、代码块时线程中断。
public class Test{
public static void main(String args[]){
sayhello say = new sayhello();
Thread cat = new Thread(say);
Thread dog = new Thread(say);
dog.setName("dog");
cat.setName("cat");
cat.start();
dog.start();
}
}
class sayhello implements Runnable{
private String s = "hello";
public synchronized void shushu(){
String name = Thread.currentThread().getName();
for(int i = 0; i < 1000; i++){
System.out.printf("%s:%d\n",name,i);
}
}
@Override
public void run() {
String name = Thread.currentThread().getName();
for(int i = 0; i < 1000; i++){
System.out.printf("%s%d\n",name,i);
}
shushu();
System.out.println(name+":goodbye");
}
}