public class TraditionalThreadSynchronized {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
new TraditionalThreadSynchronized().init();
}
private void init(){
/**内部类不能访问局部变量,所以这里要用final*/
final Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("GavinlinHere");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("JiaoZhaoHere");
}
}
}).start();
}
static class Outputer{
private String name;
/**
* 可以与outputsync同步,不可与static的同步
* @param name
*/
public void output(String name){
this.name = name;
int len = this.name.length();
synchronized(this){
for(int i = 0;i < len; i++){
System.out.print(this.name.charAt(i));
}
System.out.println();
}
}
public synchronized void outputsync(String name){
this.name = name;
int len = this.name.length();
for(int i = 0;i < len; i++){
System.out.print(this.name.charAt(i));
}
System.out.println();
}
/**
* 若想与output同步,必须output的锁为Outputer.class
* @param name
*/
public static synchronized void sOutputSync(String name){
int len = name.length();
for(int i = 0;i < len; i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
}
output,outputsync 现在这2个方法互斥吗?如果有一个线程在执行output,另外一个线程不能执行outputsync对吧?当:当前线程执行完毕了是怎么通知另外一个线程去执行outputsync的?
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
new TraditionalThreadSynchronized().init();
}
private void init(){
/**内部类不能访问局部变量,所以这里要用final*/
final Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("GavinlinHere");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("JiaoZhaoHere");
}
}
}).start();
}
static class Outputer{
private String name;
/**
* 可以与outputsync同步,不可与static的同步
* @param name
*/
public void output(String name){
this.name = name;
int len = this.name.length();
synchronized(this){
for(int i = 0;i < len; i++){
System.out.print(this.name.charAt(i));
}
System.out.println();
}
}
public synchronized void outputsync(String name){
this.name = name;
int len = this.name.length();
for(int i = 0;i < len; i++){
System.out.print(this.name.charAt(i));
}
System.out.println();
}
/**
* 若想与output同步,必须output的锁为Outputer.class
* @param name
*/
public static synchronized void sOutputSync(String name){
int len = name.length();
for(int i = 0;i < len; i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
}
output,outputsync 现在这2个方法互斥吗?如果有一个线程在执行output,另外一个线程不能执行outputsync对吧?当:当前线程执行完毕了是怎么通知另外一个线程去执行outputsync的?
1、现在这两个方法不是互斥的,因为你output方法的前两行没有在同步块儿里。
既然不适合互斥的,就不能保证有一个线程在执行output,另外一个线程不能执行outputsync。
2、当前线程执行完毕了是怎么通知另外一个线程,建议楼主看一下Object的wait和notify方法。
可以防止多个线程对"同一个对象"的同一个方法进行同时访问。
当运行到此类或此类中的相应synchronized方法,就会进入“同步”状态,其他人要调用此类必须等待。2.对静态方法加synchronize
给动态方法加锁与给静态方法加锁的机制是不一样的。
对静态方法加synchronize的效果:
相当于对“类”进行加锁。使用静态方法时,并不需要建立实例。因此其他地方要调用同一个类中的两个不同synchronize的静态方法时,将会依次等待执行。也就是说,是在同一个类的几个静态方法中存在线程互斥作用。
2. Google "wait notify".
首先看下有这么个类:public class A {
public synchronized static void methodA() {
System.out.println("methodA");
}
public synchronized static void methodB() {
System.out.println("methodB");
}
public synchronized void methodC() {
System.out.println("methodC");
}
public synchronized void methodD() {
System.out.println("methodD");
}
public void methodE() {
System.out.println("1");
System.out.println("2");
System.out.println("3");
synchronized {
System.out.println("methodE,synchronized程序块");
}
} public void methodF() {
System.out.println("methodE");
synchronized {
}
}
}
动态方法的synchronized,是指对同一个对象的方法进行加锁,这个锁是加在对象上的。
比如,在另一个类class B中,A temp = new A();
methodC和methodD都有锁,这两个方法是动态方法,存在互斥作用。例1:线程1“正在执行”temp.methodC();线程2要执行temp.methodC(),线程3要执temp.methodD(),那么线程2、线程3都需要需要等待线程1执行完temp.methodC()。线程1执行完后,线程2、线程3“竞争”执行权利(也就是要争夺“同步锁”),因为都是要运行加锁的动态方法,所以只有1个线程能运行。
总而言之,不管线程有多少个,“同一个对象”一次只能执行一个“加了锁的动态方法”。注意这里的措辞,是“同一个对象”,“加了锁的动态方法”。例2:线程1“正在执行”temp.methodC();线程2要执行temp.methodC(),线程3要执temp.methodD();线程4要执行temp.methodE();线程5要执行temp.methodF();
因为methodC、methodD都是加锁的动态方法,所以线程2、线程3必须等待线程1释放同步锁;
methodE、methodF没有加锁,所以线程4、线程5能直接运行;
所以同时是线程1、线程4、线程5在运行,线程2、线程3在等待线程1释放同步锁。
methodE()没有加锁,所以三个线程能同时运行。
但methodE()当中有同步块,假设线程2先运行到同步块,那么线程2先执行,而线程1、线程3需要等待。例4:线程1要执行temp.methodC();而线程2要执行temp.methodD()
因为都是同一个对象“temp”,而methodC、methodD都是加了锁的动态方法,因此只有一个方法能执行。由线程1、线程2竞争同步锁,这个竞争结果是随机的,但不管如何,结果都只有其中一个能执行。这就是楼主所说的“线程互斥”情况。例5:线程1要执行temp.methodC();而线程2执行A.methodA();
methodC是加锁的“动态”方法,而methodA是加锁的“静态”方法;动态方法与静态方法运行时在内存中是存放在两个不同的区域,因此两者并不冲突。所以线程1、线程2能同时运行。加锁动态方法与另外的加锁动态方法互斥,而加锁静态方法也只与静态方法互斥。例6:线程1执行A.methodA();线程2执行A.methodB();
两者都是加锁的静态方法,所以只有其中之一能执行,另一个必须等待。
methodC加了锁,但是methodF没有加锁,这两者不存在冲突,所以线程1、线程2能同时执行。例8:线程1执行A.methodA();线程2执行temp.methodF();
methodA加了锁,但是methodF没有加锁,这两者也不存在冲突,所以线程1、线程2也能同时执行。接下来是wait()、notify()的一个参考程序。
是两个线程轮流执行的程序。写这个程序费了我不少时间……
public class Test {
//int money = 0;
//为了保证能线程同步操作money对象,此处用了一个内部类Money。
//该内部类中有加了同步锁的plusOrMinus方法。
Money money = new Money(0);
MyThread1 m1;
MyThread2 m2;
// 构造方法一般只赋值,不调用具有效果的方法。
public Test() {
m1 = new MyThread1();
m2 = new MyThread2();
} public static void main(String[] args) {
// 实例化一个对象
Test w = new Test();
// 通过实例对象,再调用具有效果的方法
w.startProgram();
} public void startProgram() {
//启动线程1
//线程1可以视为主线程。一般而言都必须有一个主线程
new Thread(m1).start();
//启动线程2
new Thread(m2).start();
} // 提供一个Thread的包装类
// class myThread1 extends Thread
private class MyThread1 implements Runnable {
boolean overFlag;
public void run() {
// 线程尽量不要死循环
// while(true) {
while (!overFlag) {
try {
//money++;
//注意,此处money是线程1、线程2都会操作的对象,两个线程可能同时进行操作。
//若不同步,将可能遗漏某次冲突操作,因此对money的操作才是同步的关键。
//这里实现同步的原理,就是使用set、get器。
//将money作为一个内部类包装起来,然后使用一个synchronize的方法操纵money增减,这样对money的操作就是同步的了。
money.plusOrMinus(1);
//因受m2影响,此处打印出来的数据会有误差。
//但是,若getMoney()加了synchronized修饰,就会没有误差。
//注意看连着两次增加或连着两次减少的值。这里减少是一次性-2,但连着两次都只是-1。
//实际money的数值增减是没问题的,是打印的时候getMoney方法没有同步。
//可以将plusOrMinus中打印语句的注释去掉,对比误差数据。
System.out.println("(存在误差)m1增加money,money值为---" + money.getMoney());
//该线程是一直增加的,从不中断。
//当money > 20的时候,唤醒线程2,允许减少money。
//注意此处的this传递的对象。
//因为此句是在内部类MyThread1范围内,因此传递的是MyThread1的实例对象。
//如果要在内部类调用外部类的对象,需使用:Test.this,也就是在this前加外部类的名字。
countmoney(this);
Thread.sleep(60);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private class MyThread2 implements Runnable {
boolean overFlag;
public void run() {
while (!overFlag) {
try {
//将自身对象传到countmoney方法,进行判断,看是否需要挂起线程m2。
//money < 10的时候,挂起线程m2。
//唤醒线程则应该分开,必须在另一个“一直运行”的线程中执行。
countmoney(this);
//判断了之后再减少money。
//money -= 2;
//注意,此处money是线程1、线程2都会操作的对象,两个线程可能同时进行操作。
//若不同步,将可能遗漏某次冲突操作,因此对money的操作才是同步的关键。
//这里实现同步的原理,就是使用set、get器。
//将money作为一个内部类包装起来,然后使用一个synchronize的方法操纵money增减,这样对money的操作就是同步的了。
money.plusOrMinus(-2);
//因受m1影响,此处打印出来的数据会有误差。
//但是,若getMoney()加了synchronized修饰,就会没有误差。
//注意看连着两次增加或连着两次减少的值。有些减少是一次性-2,但有些连着两次都只是-1。
//实际money的数值增减是没问题的,是打印的时候getMoney方法没有同步。
//可以将plusOrMinus中打印语句的注释去掉,对比误差数据。
System.out.println("(存在误差)!!m2减少money,money值为---" + money.getMoney());
Thread.sleep(60);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public synchronized void countmoney(Object temp) {
//判断是否为MyThread2的实例
if (temp instanceof MyThread2) {
//money < 10的时候,挂起线程m2。
if (money.getMoney() < 10) {
try {
//必须先打印,否则wait()执行后就不会执行后面的语句了。
System.out.println("------------------------m2挂起------------------------");
//此处为什么wait前面不用加对象?
//注意main方法,生成了一个实例w。
//线程1、线程2,都是在实例w的范围内执行,因此默认直接指向实例w。
//前面实际省略了this.
wait();
//下面这句与上面这句含义相同,效果一样,打印this就知道意思了。
//this.wait();
//System.out.println(this);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判断是否为MyThread1的实例
if (temp instanceof MyThread1) {
//money>20时,唤醒m2,允许减少money。
if (money.getMoney() > 20) {
//同理,此处也省略了this.
//this指向main中的实例w。
notify();
System.out.println("------------------------m2唤醒------------------------");
}
}
}
private class Money {
int money;
public Money(int x) {
this.money = x;
}
//该方法没有同步,因此在打印的时候就会出现数值误差。
//public synchronized int getMoney() {
//之后再加同步锁,就可以发现没有误差了。
public int getMoney() {
return money;
}
//该方法加了同步,就能保证money对象能被同步操作。
public synchronized void plusOrMinus(int x) {
this.money +=x;
//该数据是在同步方法内的,因此打印的是正确的。
//System.out.println("(正确数据)money值被修改为---" + this.getMoney());
}
}
}//对上面程序的一些总结:
//1.线程尽量使用implements方法。
//使用implements的线程,赋值、启动方式如下:
//①线程构造语句为:
//private class MyThread implements Runnable
//②常用的启动语句如下:
//放在一个动态方法里包装起来
//public void startProgram() {
// new Thread(new MyThread()).start();
// //中间new MyThread1()是一个匿名的线程对象。
// //可以放进已经实例化了的线程对象,因此可以变化为:
// //MyThread m1 = new MyThread();
// //new Thread(m1).start();
// //在下面的程序里,也是采用的这种语句方案。
//}
//
//2.main方法里面的程序,一般都是生成一个实例,然后再通过实例调用一个动态方法。
//public static void main(String[] args) {
// // 实例化一个对象
// Test w = new Test();
// // 通过实例对象,再调用具有效果的方法
// w.startProgram();
//}
//
//3.wait、notify,需要通过实例才能调用。
//注意其“对象实例”不是指线程实例,因为线程实例只是内部类(private class或者不加修饰符的class就是内部类)。
//此处的“对象实例”是指public类的实例,比如Test w = new Test();这里Test就是一个public类。
//此public类生成的实例,可以再创建线程。因此,一定要注意搞清楚“public类实例”与“线程实例”的关系。
//也就是说,“public类实例”可以包含若干个线程实例,也就是多个线程。
//下面程序中的m1、m2线程对象,都是包含在“public类实例”w对象里面的。
//必须通过“public类实例”来调用wait、notify。这个在程序中也做了注释。
//
//4.在countmoney方法中,使用了instanceof方法。
//instanceof方法是判断对象之间的“类型”是否相同。如,Object instanceof int、Object instanceof char等。
//用法和==符相似,==符用于判断对象的“引用”是否相同。
//此处就是为了判断传进来的对象是否是目标线程的实例。
//
//5.money才是真正要被同步的对象。具体做法见程序中的注释。
//在money的plusOrMinus方法中有正确数据的打印,注意去掉注释查看打印结果,和误差数据进行比较。
//
//如果money是一个自定义类,比如有个专门的public类Money,那么,是否需要修改Money.java的方法,添加synchronized修饰符呢?
//在实际开发中,这样是必须避免的。可以在自己的程序里写个内部类,然后extends原始类,然后再覆写需要添加synchronized的方法。
//例:
//已经存在一个Money类:
// public class Money {
// int money;
// public Money(int x) {
// this.money = x;
// }
// public int getMoney() {
// return money;
// }
// public void plusOrMinus(int x) {
// this.money +=x;
// }
// }
//注意,plusOrMinus没加synchronized。
//在自己程序里,就写个内部类,继承Money,然后覆写plusOrMinus方法:
// private class MyMoney {
// public synchronized void plusOrMinus(int x) {
// this.money +=x;
// }
// }
//这样就不用修改Money的原始类。因为Money原始类,可能在其他地方也被用到了,其他地方没必要synchronized,如果在原始类上面修改,就会影响到其他地方的运行效率。
//
//6.一般都必须提供一个主线程。“从不中断”的线程,即可以选为主线程。主线程一般都必须有个监听方法,用于控制其他线程。
//该程序中,我就把线程1设置为主线程,然后countmoney作为监听方法,控制线程2的挂起/唤醒。
//假如线程1不是“从不中断”的线程,比如增加到20就挂起,而线程2的条件也修改为减少到20就挂起,那么,有可能出现两个线程同时挂起的情况,这就会导致程序“假死”。
//因此,一般都是再提供一个线程,使用while(1){}进行“死循环”,保持“从不中断”的状态,然后在程序中不断监听其他线程情况,并执行相应操作。
//如,提供线程1、线程2、线程3,其中线程3使用while(1){}进行死循环监视线程1、线程2,防止出现所有线程都被挂起的情况。