版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
-
线程的运行方式
-
并发运行
•线程中并发指一个时间段中多个线程都处于已启动但没有运行结束的状态。
•多个线程之间默认并发运行,这种运行方式往往会出现交叉的情况。
例如这段代码中,线程1与线程2的执行顺序是没有顺序的且不断交替,这一点在之前线程1的博客中已经写到,故不再细述。
-
串行运行
•使原本并发运行的多个线程实现串行运行,即多线程间同步执行,需要通过对象锁机制来实现,synchronized就是一个利用锁实现线程同步的关键字。
package com.thread;
import java.util.Date;
public class Test {
public static void main(String[] args) {//主线程
Object object = new Object(); //创建object对象
new CounterThread("线程1",object).start();
new CounterThread("线程2",object).start();
}
}
class CounterThread extends Thread {//计数线程
Object obj; //定义属性ojbect
CounterThread(String name,Object obj) { //将object类对象赋给创建的线程类对象
super(name);
this.obj = obj;
}
public void run() {
int i = 0;
synchronized (obj) { //利用参数obj对象锁住两个线程
for (; i < 50; i++) {
System.out.println(getName() + i);
}
}
}
}
输出结果为
可以看到,在线程1完全执行完后线程2再开始执行,但这种情况不能完全排除是无序中一次偶然的“有序”,也就是说不能完全证明synchronized锁锁住了两个线程。
再用一段代码示例
package com.thread;
import java.text.*;
import java.util.Date;
public class Test {//主线程
public static void main(String[] args) {// a.主线程开始
Object lockObj = new Object();// b.创建对象,作为synchronized锁的钥匙
new DisplayThread(lockObj).start();// c.开启显示器线程
}
}
class DisplayThread extends Thread {//显示器线程
Object lockObj;
public DisplayThread(Object lockObj) {
this.lockObj = lockObj;//创建有参构造方法以传入object对象
}
@Override
public void run() {// d.进行到这里,主线程已终止,显示器线程开始执行run方法,而且目前只有显示器线程这一个线程
synchronized (lockObj) {// e.因为这时显示器线程没有“竞争对手”,所以一定是它先解锁,开始执行解锁的代码
new TimeThread(lockObj).start();// f.进行到这里时间线程开启,也就是此时“钥匙”(object对象)依旧被显示器线程占用,也就说明时间线程必须等到显示器线程终止才能解锁
try {
sleep(60000);// g.这时显示器线程进入60秒的阻塞状态,但object类对象依旧被占用,60秒后,显示器线程终止
} 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锁住,所以可以正常执行。
synchronized (lockObj) {// h.显示器线程终止后,时间线程被解锁
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
String time = dateFormat.format(new Date());
System.out.println(time);// i.输出时间
}
}
}
正常情况下,由于先执行的显示器线程进入了阻塞状态,时间线程应该立马开始执行,输出两个语句,时间几乎相同,但实际的输出为
间隔了一分钟,说明synchronize锁由于显示器线程先拿到了钥匙,其未执行完毕时即使是阻塞期间,时间线程也无法拿到钥匙开始运行。
注意:synchronize锁中传入的参数需要具有唯一性,同时只要某个对象有唯一性,那么它就可以作为传入锁中的参数,例如某些非实例化对象
package com.thread;
public class Test {//实例1
public static void main(String[] args) {
new CountThread().start();
new CountThread().start();
}
}
class CountThread extends Thread{
@Override
public void run() {
synchronized ("Tom") {//这里传入的是常量池中的字符串,具有唯一性,所以能实现串行化
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + "--->" + i);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
-
死锁现象
概念:
如果有两个或两个以上的线程都访问了多个资源,而这些线程占用了一些资源的同时又在等待其它线程占用的资源,也就是说多个线程之间都持有了对方所需的资源,而又相互等待对方释放的资源,在这种情况下就会出现死锁。
public class DeadLockThread {
// 创建两个线程之间竞争使用的对象
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new ShareThread1().start();//线程1开启
new ShareThread2().start();//线程2开启
}
private static class ShareThread1 extends Thread {
public void run() {
synchronized (lock1) {//此时lock1类对象被占用
try {
Thread.sleep(50);//线程1阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {//lock2被线程2占用,线程1再次阻塞
System.out.println("ShareThread1");
}
}
}
}
private static class ShareThread2 extends Thread {
public void run() {
synchronized (lock2) {//此时lock2被占用
try {
Thread.sleep(50);//线程2阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {//lock1被线程1占用,线程2再次阻塞
System.out.println("ShareThread2");
}
}
}
}
}