同步
一个任务的执行,需要依赖另一个任务的完成。
标准的程序执行流程就是同步的,只有上一个函数执行完成,下一个函数才可以开始执行,这就是依赖关系
异步
一个任务的执行,不需要依赖另一个任务的完成。
并发的概念,只需要告诉另一个任务需要干什么,它会直接去执行
对象锁:只有竞争同一个对象的时候
- synchronized修饰
非静态方法
、代码块synchronized (this)
、synchronized (非this对象)
线程要执行对应的同步代码,需要获取对象锁
锁住方法
先展示一个没有锁的例子,他的输出结果根据代码可以知道,因为我们实例化了一个对象,然后先开启了一个线程传了一个参数a
过去,执行到System.out.println("a arrived");
他就开始睡眠2s,于此同时又开启了另一个线程,同样调用的是同一对象的同一个方法,传递了b
过去,所以出现下面这个结果
public class main {
public static void main(String[] args) throws IOException {
person p = new person();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.addstr("a");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.addstr("b");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
class person{
private int number = 0;
public void addstr(String string) throws InterruptedException {
if (string.equals("a")) {
number = 100;
System.out.println("a arrived");
Thread.sleep(2000);
}else {
number = 200;
System.out.println("b arrived");
}
System.out.println(string + " number=" + String.valueOf(number));
}
}
输出结果:
a arrived
b arrived
b number=200
a number=200
稍作更改给addstr
方法加上锁,这时候多线程竞争同一个对象的带锁的方法时,就要按顺序来,要等上一个调用释放锁
class person{
private int number = 0;
synchronized public void addstr(String string) throws InterruptedException {
if (string.equals("a")) {
number = 100;
System.out.println("a arrived");
Thread.sleep(2000);
}else {
number = 200;
System.out.println("b arrived");
}
System.out.println(string + " number=" + String.valueOf(number));
}
}
还有一种情况就是,锁住一个类的两个方法,两个线程同时调用锁住的不同对象,这种情况,经过测试,输出结果显示,执行第二个被锁住的方法也需要等待,也就是一个类内,使用一个锁,锁住一个或多个方法
。
person p = new person();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.printA(string);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.printB(string);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
class person{
private int number = 0;
synchronized public void printA(String string) throws InterruptedException {
System.out.println("A 进入线程!");
Thread.sleep(2000);
System.out.println("A 离开线程!");
}
synchronized public void printB(String string) throws InterruptedException {
System.out.println("B 进入线程!");
System.out.println("B 离开线程!");
}
}
输出结果:
A 进入线程!
A 离开线程!
B 进入线程!
B 离开线程!
synchronized (this)
再次对上面的person类稍作修改,我们锁住一个代码块,同样的当多个线程竞争同一个对象
的这个代码块
时需要按照锁的规矩来,上一个释放,下一个才可以使用,输出结果同上
class person{
private int number = 0;
public void addstr(String string) throws InterruptedException {
synchronized(this) {
if (string.equals("a")) {
number = 100;
System.out.println("a arrived");
Thread.sleep(2000);
}else {
number = 200;
System.out.println("b arrived");
}
System.out.println(string + " number=" + String.valueOf(number));
}
}
}
这里需要注意一下,因为多线程只有竞争这个代码块的内容才是使用锁的规矩,也就是这个代码块上面、下面都是不同步的,修改一下代码,这个这个同步代码块上面、下面加上两句输出System.out.println(string + "来了");、System.out.println(string + " number=" + String.valueOf(number));
,这个时候输出结果有下面两种结果。首先解释一个b来了
的输出,因为System.out.println(string + "来了");
这一句没有锁,所以在第一个带有参数a
睡眠的时候,第二个线程运行到这里输出完b来了
,它就在这里等着,等第一个线程释放了锁,它才能继续执行。至于结果有两种,则是因为另外一个输出语句System.out.println(string + " number=" + String.valueOf(number));
没有上锁,完全根据谁快、谁执行,或者并发执行都是可以的
class person{
private int number = 0;
public void addstr(String string) throws InterruptedException {
System.out.println(string + "来了");
synchronized(this) {
if (string.equals("a")) {
number = 100;
System.out.println("a arrived");
Thread.sleep(2000);
}else {
number = 200;
System.out.println("b arrived");
}
}
System.out.println(string + " number=" + String.valueOf(number));
}
}
输出结果:
a来了
a arrived
b来了
b arrived
a number=100
b number=200
或者
a来了
a arrived
b来了
b arrived
b number=200
a number=200
同样的,synchronized (this)也存在同一个类中,使用一个锁锁住一个或多个代码块,但是代码块上下是不同步的
class person{
public String son;
public person(String son) {
this.son = son;
}
private int number = 0;
synchronized public void printA(String string) throws InterruptedException {
System.out.println("A 进入线程!");
Thread.sleep(2000);
System.out.println("A 离开线程!");
}
public void printB(String string) throws InterruptedException {
System.out.println("B ready!");
synchronized (this) {
System.out.println("B 进入线程!");
System.out.println("B 离开线程!");
}
}
输出结果:
A 进入线程!
B ready!
A 离开线程!
B 进入线程!
B 离开线程!
synchronized (非this对象)
只要多线程访问的对象带有被锁住的对象,并且是同一个,就需要按照锁的规则来,就像下面的例子,虽然实例化了两个不同的对象,但是他们访问的都是同一个String对象,就有竞争关系。其实可以被锁住的对象就像一个标记,任何线程使用了这个被锁住的对象,就相当于被标记了,被标记的线程执行到synchronized声明的地方,就要按顺序来,不存在并发进行
public static void main(String[] args) throws IOException {
String string = "son";
person p = new person(string);
person p1 = new person(string);
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.addstr("a");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p1.addstr("b");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
class person{
public String son;
public person(String son) {
this.son = son;
}
private int number = 0;
public void addstr(String string) throws InterruptedException {
System.out.println(string + "来了");
synchronized(son) {
if (string.equals("a")) {
number = 100;
System.out.println("a arrived");
Thread.sleep(2000);
}else {
number = 200;
System.out.println("b arrived");
}
}
System.out.println(string + " number=" + String.valueOf(number));
}
}
类锁
- synchronized修饰
静态方法
、synchronized(类.class)
,锁的是对应的类,需要获取类锁
类锁其实也是使用对象锁,当加载一个class文件时,会创建一个java.lang.Class类的实例,锁一个类的时候就是锁住java.lang.Class类的实例
下面这个例子,是对象锁
,使用不同person对象
,即使用了不同的锁锁住person对象
String string = "son";
person p = new person(string);
person p1 = new person(string);
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.printA(string);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p1.printB(string);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
class person{
public String son;
public person(String son) {
this.son = son;
}
private int number = 0;
synchronized public void printA(String string) throws InterruptedException {
System.out.println("A 进入线程!");
Thread.sleep(2000);
System.out.println("A 离开线程!");
}
synchronized public void printB(String string) throws InterruptedException {
System.out.println("B ready!");
System.out.println("B 进入线程!");
System.out.println("B 离开线程!");
}
}
对象锁
的输出结果:
A 进入线程!
B ready!
B 进入线程!
B 离开线程!
A 离开线程!
稍作修改加上static
,让我们使用类锁
,即使实例化了不同的对象,但是使用的同一个类,这时候的输出结果就不一样了,这里还用了同一个类,使用一个锁锁住一个或多个方法、代码块
class person{
public String son;
public person(String son) {
this.son = son;
}
private int number = 0;
synchronized public static void printA(String string) throws InterruptedException {
System.out.println("A 进入线程!");
Thread.sleep(2000);
System.out.println("A 离开线程!");
}
synchronized public static void printB(String string) throws InterruptedException {
System.out.println("B ready!");
System.out.println("B 进入线程!");
System.out.println("B 离开线程!");
}
}
输出结果:
A 进入线程!
A 离开线程!
B ready!
B 进入线程!
B 离开线程!
再次引入一个小心得点:类锁
和对象锁
混用
同样使用上面的方法代码稍微修改了一下,可以从输出结果看出,使用对象锁的代码块,即使是同一个对象,也没有执行锁的机制,说明了类锁和对象锁混用的情况下,只有类锁有效,对象锁无效
this: 表示当前类实例化后的对象
person p = new person(string);
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.printA(string);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
p.printB(string);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
class person{
public String son;
public person(String son) {
this.son = son;
}
private int number = 0;
synchronized public static void printA(String string) throws InterruptedException {
System.out.println("A 进入线程!");
Thread.sleep(2000);
System.out.println("A 离开线程!");
}
public void printB(String string) throws InterruptedException {
System.out.println("B ready!");
synchronized (this) {
System.out.println("B 进入线程!");
System.out.println("B 离开线程!");
}
}
}
为了证明和锁的顺序没关系,调换顺序来证明
class person{
public String son;
public person(String son) {
this.son = son;
}
private int number = 0;
public void printA(String string) throws InterruptedException {
System.out.println("A ready!");
synchronized (this) {
System.out.println("A 进入线程!");
Thread.sleep(2000);
System.out.println("A 离开线程!");
}
}
synchronized public static void printB(String string) throws InterruptedException {
System.out.println("B 进入线程!");
System.out.println("B 离开线程!");
}
}
输出结果:
A ready!
A 进入线程!
B 进入线程!
B 离开线程!
A 离开线程!