参考博客
关于多线程,都是根据这个博客进行整理的
在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。
只要资源没有发生变化,多个线程读取相同的资源就是安全的。
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件
。导致竞态条件发生的代码区称作临界区
在临界区中使用适当的同步就可以避免竞态条件。
Java中的同步块用synchronized
标记。同步块在Java中是同步在某个对象上,事实上在java中,任何一个对象都可以作为一个锁。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
实例方法同步
在方法声明中同步(synchronized )关键字
多个线程要运行的是同一个对象实例的同步方法,如果一个每个线程运行的是不同的对象实例的同步方法,是没有同步效果的,因为每个对象实例是把自身当成锁,就导致没有公用一个锁。
静态方法同步
静态方法同步和实例方法同步方法一样,也使用synchronized 关键字。 public static synchronized
静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
实例方法中的同步块
public void add(int value){
synchronized(this){
this.count += value;
}
}
示例使用Java同步块构造器来标记一块代码是同步的。该代码在执行时和同步方法一样。
注意Java同步块构造器用括号将对象括起来。在上例中,使用了“this
”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。
一次只有一个线程能够在同步于同一个监视器对象的Java方法内执行。
什么样的变量可以成为共享资源?
允许被多个线程同时执行的代码称作线程安全
的代码。线程安全的代码不包含竞态条件。当多个线程同时更新共享资源时会引发竞态条件。
局部变量
存储在线程自己的栈
中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。
对象成员存储在堆
上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。
线程控制逃逸规则
如果一个资源的创建,使用和销毁都在同一个线程内完成,且永远都有不脱离该线程的控制,那么该资源的使用就是线程安全的
即使对象本身线程安全,但如果该对象中包含其他资源(文件,数据库连接),整个应用也许就不再是线程安全的了。比如2个线程都创建了各自的数据库连接,每个连接自身是线程安全的,但它们所连接到的同一个数据库也许不是线程安全的
线程安全及不可变性
当多个线程同时访问同一个资源,并且其中的一个或者多个线程对这个资源进行了写操作,才会产生竞态条件。多个线程同时读同一个资源不会产生竞态条件。
可以通过创建不可变的共享对象来保证对象在线程间共享时不会被修改,从而实现线程安全。
即使一个对象是线程安全的不可变对象,指向这个对象的引用也可能不是线程安全的。
同步代码块与隐式锁
synchronized
关键字的作用就是用于声明这是一段同步代码块。JVM在遇到synchronized关键字时,会把花括号"{...}"中间的代码当成一个原子操作
,也就是说,只有等到同步代码块中的代码在执行完成的时候,CPU才会进行线程的上下文切换,而不会再同步代码块中的内容只执行了一部分的时候,就切换到其他线程运行。
每个synchronized关键字都必须要配合锁
进行使用,在java中,任何对象实例都可以当做一个锁来使用
在java官方的并发编程教程中,就提到每一个java对象都会关联一个隐式锁,因此当我们在使用synchronized关键字编写同步代码块时,实际上利用的就是小括号中的java对象的关联的隐式锁。
在使用同步代码块解决多线程竞争共享资源的问题时,我们使用的必须是同一把锁。所谓同一把锁
,其实指得就是同一个对象实例。多个线程竞争同一把锁,先得到锁的先执行,在同步代码块执行完成之后,就把自动把锁释放掉。然后其他线程再来抢夺这把锁,进行代码的执行。
如果使用了不同的锁,两个线程依然可以并发执行
Java类锁、对象锁、私有锁、隐式锁
类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段
对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段
私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock)
类锁和对象锁不会产生竞争,二者的加锁方法不会相互影响。
私有锁和对象锁也不会产生竞争,二者的加锁方法不会相互影响。
synchronized直接加在方法上和synchronized(this)都是对当前对象加锁,二者的加锁方法够成了竞争关系,同一时刻只能有一个方法能执行。
同步方法与同步代码块的区别
在同步代码块中,我们可以自由的选择锁。
在同步代码块中,我们可以自由的选择任何一个java对象实例作为同步过程中要使用到的锁。但是对于实例同步方法而言,这个锁是不能选择的,就是这个对象实例。对于静态同步方法而言,这个锁就是类的class对象实例。
我们只需要对临界区
的代码进行同步
因为多线程只会对临界区的代码访问顺序敏感,因此在执行同步操作的时候,如果使用的是同步方法,那么整个方法中的所有内容都会被当做一个原子操作。而事实上在大多数情况下,我们可能只是方法中某一段内容需要同步,同步代码块可以帮助我们只在必要的地方进行同步。
当然,如果方法中的所有内容的确都是要当做一个原子操作进行,那么此时同步代码块和同步方法其实效果是一样的。
wait()、notify()、notifyAll()与线程通信方式总结
一个线程一旦调用了任意对象
的wait()方法,就会变为非运行状态
,直到另一个线程调用了同一个对象的notify()方法。为了调用 wait()或者notify(),线程必须先获得那个对象的锁。也就是说,线程必须在同步块里调用wait()或者notify()。
sleep()方法与wait方法的区别
1、sleep()方法是Thread对象中定义的方法,而wait()方法定义在Object类中
2、可以在任意地方调用线程对象的sleep方法,但是wait()方法必须位于同步代码块或者同步方法中
3、线程在sleep的时候,并不会释放锁,因此其他线程无法获取到锁,因此也无法执行。而wait方法在执行的时候会释放锁,因此其他线程可以获取到锁,可以有机会运行。
到现在,才真正的了解三个线程循环打印ABC的代码
public class Thread2 implements Runnable {
String name;
Object prve;
Object now;
public Thread2(String name, Object prve, Object now) {
super();
this.name = name;
this.prve = prve;
this.now = now;
}
@Override
public void run() {
for(int i = 0;i<10;i++) {
synchronized(prve) {
synchronized (now) {
System.out.print(name);
now.notify();
}
try {
prve.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
Object c = new Object();
Thread t1 = new Thread(new Thread2("A", c, a));
Thread t2 = new Thread(new Thread2("B", a, b));
Thread t3 = new Thread(new Thread2("C", b, c));
t1.start();
Thread.sleep(100);
t2.start();
Thread.sleep(100);
t3.start();
Thread.sleep(100);
}
}