并发运行与串行运行
多个线程之间默认并发运行,这种运行方式往往会出现交叉的情况。首先引入一段代码来说明这种情况:
package 线程间数据共享;
public class Test {
public static void main(String[] args) {
new CountThread("@@@@").start();
new CountThread("###############").start();
}
}
class CountThread extends Thread{
CountThread(String name){
super(name);
}
@Override
public void run() {
for(int i = 0;i<50;++i) {
System.out.println(getName()+","+i);
}
}
}
结果显示为:
synchronized关键字
synchronized同步关键字有两种使用方式:
1、声明同步方法:
public synchronized void methodName( ){
同步操作方法体
}
2、同步代码块:
synchronized (需要同步操作的对象) {
同步对象操作的语句
}
线程默认情况下以抢占式执行,因此会出现交叉执行的情况,如何解决交叉执行的问题。不强调谁先谁后,但是强调只有一个线程执行结束才执行下一个线程。因此,我们通过synchronized关键字对代码进行修改:
package 线程间数据共享;
public class Test {
public static void main(String[] args) {
Object object = new Object();
new CountThread("@@@@",object).start();
new CountThread("###############",object).start();
}
}
class CountThread extends Thread{
Object object;
CountThread(String name,Object object){
super(name);
this.object=object;
}
@Override
public void run() {
synchronized(object) {
for(int i = 0;i<50;++i) {
System.out.println(getName()+","+i);
}
}
}
}
结果显示为:
由此可以看出,通过synchronized和object对象来进行限定,从而实现了线程的依次执行,只不过执行顺序的先后没有规定。synchronized临界区中的代码,只可有且仅有一个线程执行。其中object->""或xxx.class的形式
也可以解决交叉执行。
但是,当synchronized出现在方法体中,则无法避免交叉执行:
@Override
public void run() {
synchronized(this) {
for(int i = 0;i<50;++i) {
System.out.println(getName()+","+i);
}
}
}
或者
@Override
public synchronized void run() {
for(int i = 0;i<50;++i) {
System.out.println(getName()+","+i);
}
}
结果显示为:
死锁
如果有两个或两个以上的线程都访问了多个资源,而这些线程占用了一些资源的同时又在等待其它线程占用的资源,也就是说多个线程之间都持有了对方所需的资源,而又相互等待对方释放的资源,在这种情况下就会出现死锁。
多个线程互相等待对方释放对象锁,此时就会出现死锁
public class DeadLockThread {
// 创建两个线程之间竞争使用的对象
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new ShareThread1().start();
new ShareThread2().start();
}
private static class ShareThread1 extends Thread {
public void run() {
synchronized (lock1) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("ShareThread1");
}
}
}
}
private static class ShareThread2 extends Thread {
public void run() {
synchronized (lock2) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("ShareThread2");
}
}
}
}
}
结果显示为:
我们可以看到光标浮动,但是没有结果输出。
多线程同步原理
代码展示为:
package 线程间数据共享;
import java.text.*;
import java.util.Date;
public class Test {
public static void main(String[] args) {
Object lockObj = new Object();
new DisplayThread(lockObj).start();
}
}
class DisplayThread extends Thread {
Object lockObj;
public DisplayThread(Object lockObj) {
this.lockObj = lockObj;
}
@Override
public void run() {
synchronized (lockObj) {
new TimeThread(lockObj).start();
try {
sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TimeThread extends Thread {
Object lockObj;
public TimeThread(Object lockObj) {
this.lockObj = lockObj;
}
@Override
public void run() {
System.out.println("时间线程开始执行......"+new Date());
synchronized (lockObj) {
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
String time = dateFormat.format(new Date());
System.out.println(time);//为什么这行代码10秒左右才会执行?
}
}
}
结果显示为:
从结果可以看出,执行输出时间的代码与输出“时间线程开始执行”的代码之间相差了10s。
为什么这行代码10秒左右才会执行?
显示器线程和时间线程共享lockObj对象,显示器线程优先进入启动状态,随后执行相应的run方法,当执行同步代码块时lockObj变量所代表的对象锁归显示器线程所有,进而创建时间线程并使之处于启动状态,此时有一下两种状态:
1、时间线程马上进入执行状态,马上执行该时间线程run方法,可是由于此时lockObj变量所代表的对象锁被显示器线程持有,这时时间线程进入阻塞状态,显示器线程再次执行,然后执行sleep方法,显示器线程在继续持有对象锁的前提下也进入阻塞状态,10秒后显示器线程进入执行状态,随后显示器线程结束,对象锁被释放,进而时间线程开始执行,进而这行代码运行;
2、时间线程并没有马上进入执行状态,显示器线程执行sleep方法,显示器线程在继续持有对象锁的前提下也进入阻塞状态,此时时间线程进入执行状态,执行该时间线程run方法,执行该方法中第一行输出代码,可是由于此时lockObj变量所代表的对象锁被显示器线程持有,所以时间线程并没有执行时间线程run方法内临界区中的代码,这时时间线程也进入阻塞状态,此时显示器和时间两条线程均进去阻塞状态,等待少于10秒的时间后,显示器线程进入运行状态,随后显示器线程结束,对象锁被释放,进而时间线程开始执行,进而这行代码运行。