一.并发和串行
在java线程中,默认抢占式执行,所以多个线程在同时开启时会出现交叉执行的情况,以如下这段代码为例,开启两个TimeThread线程,用for循环分别打印100语句,执行结果如下:
package tea;
import java.util.Date;
public class Test {
public static void main(String[] args) {
new TimeThread("~~~~~~~~~~~").start();
new TimeThread("$$$$$$$$$$$").start();
}
}
class TimeThread extends Thread{
public TimeThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + new Date());
}
}
}
从运行结果来看,两个线程确实遵循抢占式执行的原则,二者有来有往互不相让,那么这种交叉执行的方式就是并发执行。
还有一种执行方式叫串行执行,它便是指同时开启的线程不出现交叉执行,总是先执行完其中一个线程再去执行下一个线程。那么串行执行应该如何实现呢?这就要使用接下来要说的数据共享了。
二.synchronized数据共享
1.同一个类创建的多个线程
当多个线程都是由同一个类创建的,实现串行的数据共享操作示例如下:
执行到第8行:主线程中创建一个Object类对象object。
执行到第9、10行:开启两个TimeThread类线程,向其中分别传入线程的名字和object对象。
执行到第18行:调用父类的构造方法为本线程起名字,传入的是从主线程传过来的字符串。
执行到第19行:将主线程传过来的object对象赋值给本线程中的全局变量object,由于引用类的赋值是地址传递,所以主线程开启的两个TimeThread线程中的全局变量object所指向的是同一个Object类对象,都是在主线程中创建的object。
执行到第24行:使用synchronized共享object数据,此时被该语句括起来的代码块同一时间只能有一个线程来执行了,也就是说假如“~~~”线程先抢占到段代码的执行权,则它在执行这段代码的时候,“$”线程必须处于等待状态,直到“~”线程将该段代码块完全执行完,“$”才能处于运行状态。
package tea;
import java.util.Date;
public class Test {
public static void main(String[] args) {
Object object = new Object();
new TimeThread("~~~~~~~~~~~",object).start();
new TimeThread("$$$$$$$$$$$",object).start();
}
}
class TimeThread extends Thread{
Object object;
public TimeThread(String name,Object object) {
super(name);
this.object=object;
}
@Override
public void run() {
synchronized (object) {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + new Date());
}
}
}
}
以上这种实现串行的方式叫做对象锁,不难理解,synchronized关键字就像是一把锁,将所括起来的代码块都锁了起来,而共享的数据就相当于唯一的一把钥匙,当“$”线程执行到对象锁时,如果“~”线程正在执行锁中的代码块,共享的钥匙便在“~~”手中,此时“$$”线程必须等到“~~”线程执行完毕才能拿到对象锁的钥匙,然后进入代码块开始执行。
以下是运行结果:
2.不同类创建的多个线程
在上面的示例中,多个线程是由同一个类创建的,此时被对象锁锁住的代码块是同一段代码块,那么如果多个线程是由不同的类创建的,此时在用对象锁还能锁住不同类中的不同代码块吗?
示例如下:
在TimeThread类线程和CountThread线程中分别使用synchronized共享同一个Object类对象,用对象锁锁住不同的代码块,这时运行程序,发现执行结果还是串行的,说明即使是不同的类创建的多个线程,只要共享的数据是同一个,就可以锁住不同的代码块。
package tea;
import java.util.Date;
public class Test {
public static void main(String[] args) {
Object object = new Object();
new TimeThread("时间线程",object).start();
new CountThread("计数器线程",object).start();
}
}
class TimeThread extends Thread{
Object object;
public TimeThread(String name,Object object) {
super(name);
this.object=object;
}
@Override
public void run() {
synchronized (object) {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + new Date());
}
}
}
}
class CountThread extends Thread{
Object object;
public CountThread(String name,Object object) {
super(name);
this.object=object;
}
@Override
public void run() {
synchronized (object) {
for (int i = 0; i < 100; i++) {
System.out.println(new Date()+getName());
}
}
}
}
3.可以共享的数据
以上两个示例中,共享的数据都是一个Object类对象,然而其实只要是同一个数据都可以作为对象锁的“钥匙”的。
1.Object等类的同一个对象
2.""也可以作为共享的数据,因为它作为一个String类字符串存储在常量池中,所以synchronized时共享的一定是同一个数据。
3.XXX.class,某类的class对象也也是对象,所以也可以作为对象锁的“钥匙”。
4.this不可以,因为this代指的是调用run方法的对象,而每一个线程对象都是不同的对象,所以肯定不是同一个数据,不能作为共享的数据。
4.synchronized修饰run方法
synchronized除了可以包裹代码块以外,还可以修饰run方法,就像如下代码,但是运行结果发现还是并发执行的
package tea;
import java.util.Date;
public class Test {
public static void main(String[] args) {
new TimeThread("~~~~~~~~~").start();
new TimeThread("$$$$$$$$$").start();
}
}
class TimeThread extends Thread{
public TimeThread(String name) {
super(name);
}
@Override
public synchronized void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + new Date());
}
}
}
这是因为在run方法上加synchronized修饰就相当于用synchronized(this)包裹代码块,其效果就是以调用run方法的对象作为对象锁的钥匙,那么这种用法真的实现不了串行执行吗?其实也不然,假如像如下代码这样使用,便可以实现串行运行了:
创建TimeThread类线程实现Runnable接口,然后通过Thread类创建TimeThread类线程,这时创建的线程对象是同一个,所以即可实现串行执行,运行结果如下:
package tea;
import java.util.Date;
public class Test {
public static void main(String[] args) {
TimeThread timeThread=new TimeThread();
new Thread(timeThread,"~~~~~~~~~").start();
new Thread(timeThread,"$$$$$$$$$").start();
}
}
class TimeThread implements Runnable{
@Override
public synchronized void run() {
Thread thread = Thread.currentThread();
for (int i = 0; i < 100; i++) {
System.out.println(thread.getName() + new Date());
}
}
}