在java多线程的使用过程中,我们面临的最现实的问题就是"非线程安全的问题"——多线程导致的数据不同步问题。
注意事项:
synchronized关键字不可以加在继承于Thread类的子类的run()方法上,因为如果有当前线程类的多个对象同时被开启时,每个对象都具有各自的对象锁,都可以执行各自的run方法。这样就起不到同步的作用了。
如果非要在run()方法上加synchronized关键字,唯一的做法就是不要去继承Thread类,转而实现Runnable()接口。
/**
* 父类
*/
public class Main {
public int i=10;
synchronized public void operateIMainMethod(){
try {
i--;
System.out.println("main print i="+i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String []args){
Mythread t = new Mythread();
t.start();
}
}
/**
* 子类
*/
class Sub extends Main{
synchronized public void operateISubMethod(){
try {
while(i>0){
i--;
System.out.println("sub print i="+i);
Thread.sleep(100);
this.operateIMainMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 线程类
*/
class Mythread extends Thread{
@Override
public void run() {
super.run();
Sub sub=new Sub();
sub.operateISubMethod();
}
}
运行结果:
sub print i=9
main print i=8
sub print i=7
main print i=6
sub print i=5
main print i=4
sub print i=3
main print i=2
sub print i=1
main print i=0
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
同步与异步的区别:
* 当有多个线程同时访问某一个代码片段时,这个代码片段中有一部分代码是包含在由synchronized 修饰的同步代码块中,而另一部分则在同步代码块之外,则它们执行同步代码块中的代码被称为同步,执行同步代码块之外的代码时,被称为异步。
* 同步,同一时间只允许一个线程访问
* 异步,同一时间允许多个线程同时访问(逻辑上的同时,物理上不一定是同时的)
同步方法与同步代码块之间需要注意的地方:
**如果在一个类中有很多个synchronized同步方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率**
* 1当同步代码块中的对象为this对象时:
* 对其他用synchronized 修饰的方法或synchronized(this)同步代码块呈阻塞状态,同一时间只有一个线程可以执行同步方法或者同步代码块中的代码,即同步代码块与同步方法是同步的。
- 2.当同步代码块中使用的对象为非this对象时:
- 同步代码块的执行和同步方法的执行之间是异步的。
synchronized关键字加到非static方法上是给对象上锁;synchronized加到static方法上是给Class类上锁。对象锁只对当前对象起作用,而Class锁则对类的所有对象起作用。
* 对象锁:synchronized(this)或者synchronized(A a)
* Class锁:synchronized(A.class)
先看个例子:
public class StringTest{
public void print(String s) {
synchronized (s) {
while (true) {
System.out.println(Thread.currentThread().getName() + "--------");
}
}
}
public static void main(String []args){
StringTest st=new StringTest();
new Thread(new Runnable() {
@Override
public void run() {
st.print("a");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
st.print("a");
}
}).start();
}
}
运行的结果为:
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
由运行结果可以看到,只有一个线程被执行了,而另一个线程并没有被执行,这其实就是String常量池的原因,两个线程的run方法中调用print()方法时传入的参数都是”a”两个线程需要同一个锁对象,而Thread-0持有锁对象,就进入死循环,导致线程Thread-1得不到执行。当我们将String参数改变为Object对象时,就可以解决上述问题,如下所示:
public class StringTest{
public void print(Object object) {
synchronized (object) {
while (true) {
System.out.println(Thread.currentThread().getName() + "--------");
}
}
}
public static void main(String []args){
StringTest st=new StringTest();
new Thread(new Runnable() {
@Override
public void run() {
st.print(new Object());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
st.print(new Object());
}
}).start();
}
}
运行结果为:
Thread-0——–
Thread-0——–
Thread-0——–
Thread-0——–
Thread-1——–
Thread-1——–
Thread-1——–
Thread-1——–
Thread-1——–
Thread-1——–
所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
产生死锁的现象:
* synchronized嵌套的代码结构:
synchronized(lock1){
...
synchronized(lock2){
...
}
...
}
关键字volatile的主要作用是使变量在多个线程间可见。它的作用是强制从公共堆栈中取得变量的值,而不是从线程的私有数据栈中取得变量的值。
我们来看一个例子:
public class VolatileTest extends Thread{
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
super.run();
System.out.println("进入run了");
while(isRunning==true){
}
System.out.println("线程被停止了");
}
public static void main(String []args){
try {
VolatileTest vt=new VolatileTest();
vt.start();
Thread.sleep(1000);
vt.setRunning(false);
System.out.println("已经赋值为false");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果为:
进入run了
已经赋值为false
由此可以断定程序进入了死循环当中,(原因:在启动VolatileTest .java线程时,变量private boolean isRunning = true;存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程的运行效率,线程一直在私有堆栈中进行取值。而代码vt.setRunning(false);虽然被执行,但它更新的是公共堆栈中的数据,线程的私有堆栈数据并没有与之一起变化,最终导致了死循环)下面我将通过Volatile关键字来解决此问题(Volatile关键字会在线程访问上述代码中的isRunning 时,强制从公共堆栈中进行取值):
public class VolatileTest extends Thread{
volatile private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
super.run();
System.out.println("进入run了");
while(isRunning==true){
}
System.out.println("线程被停止了");
}
public static void main(String []args){
try {
VolatileTest vt=new VolatileTest();
vt.start();
Thread.sleep(1000);
vt.setRunning(false);
System.out.println("已经赋值为false");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果为:
进入run了
已经赋值为false
线程被停止了
根据运行结果我们会发现,之前的问题已经被我们解决了。在这里我告诉大家,其实synchronized同样具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。
我们对之前的错误代码做出一下修改,看一看运行的效果:
public class VolatileTest extends Thread{
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
super.run();
System.out.println("进入run了");
String anything=new String();
while(isRunning==true){
synchronized (anything){
}
}
System.out.println("线程被停止了");
}
public static void main(String []args){
try {
VolatileTest vt=new VolatileTest();
vt.start();
Thread.sleep(1000);
vt.setRunning(false);
System.out.println("已经赋值为false");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果为:
进入run了
已经赋值为false
线程被停止了
从结果来看synchronized确实起到了私有变量与公共变量的同步。